18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci/* 48c2ecf20Sopenharmony_ci * Copyright 2016-2020 HabanaLabs, Ltd. 58c2ecf20Sopenharmony_ci * All Rights Reserved. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "habanalabs.h" 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_cistatic bool is_dram_va(struct hl_device *hdev, u64 virt_addr) 138c2ecf20Sopenharmony_ci{ 148c2ecf20Sopenharmony_ci struct asic_fixed_properties *prop = &hdev->asic_prop; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci return hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size, 178c2ecf20Sopenharmony_ci prop->dmmu.start_addr, 188c2ecf20Sopenharmony_ci prop->dmmu.end_addr); 198c2ecf20Sopenharmony_ci} 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/** 228c2ecf20Sopenharmony_ci * hl_mmu_init() - initialize the MMU module. 238c2ecf20Sopenharmony_ci * @hdev: habanalabs device structure. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * This function does the following: 268c2ecf20Sopenharmony_ci * - Create a pool of pages for pgt_infos. 278c2ecf20Sopenharmony_ci * - Create a shadow table for pgt 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * Return: 0 for success, non-zero for failure. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ciint hl_mmu_init(struct hl_device *hdev) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci if (hdev->mmu_enable) 348c2ecf20Sopenharmony_ci return hdev->mmu_func.init(hdev); 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci return 0; 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci/** 408c2ecf20Sopenharmony_ci * hl_mmu_fini() - release the MMU module. 418c2ecf20Sopenharmony_ci * @hdev: habanalabs device structure. 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * This function does the following: 448c2ecf20Sopenharmony_ci * - Disable MMU in H/W. 458c2ecf20Sopenharmony_ci * - Free the pgt_infos pool. 468c2ecf20Sopenharmony_ci * 478c2ecf20Sopenharmony_ci * All contexts should be freed before calling this function. 488c2ecf20Sopenharmony_ci */ 498c2ecf20Sopenharmony_civoid hl_mmu_fini(struct hl_device *hdev) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci if (hdev->mmu_enable) 528c2ecf20Sopenharmony_ci hdev->mmu_func.fini(hdev); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/** 568c2ecf20Sopenharmony_ci * hl_mmu_ctx_init() - initialize a context for using the MMU module. 578c2ecf20Sopenharmony_ci * @ctx: pointer to the context structure to initialize. 588c2ecf20Sopenharmony_ci * 598c2ecf20Sopenharmony_ci * Initialize a mutex to protect the concurrent mapping flow, a hash to hold all 608c2ecf20Sopenharmony_ci * page tables hops related to this context. 618c2ecf20Sopenharmony_ci * Return: 0 on success, non-zero otherwise. 628c2ecf20Sopenharmony_ci */ 638c2ecf20Sopenharmony_ciint hl_mmu_ctx_init(struct hl_ctx *ctx) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci struct hl_device *hdev = ctx->hdev; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (hdev->mmu_enable) 688c2ecf20Sopenharmony_ci return hdev->mmu_func.ctx_init(ctx); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci return 0; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci/* 748c2ecf20Sopenharmony_ci * hl_mmu_ctx_fini - disable a ctx from using the mmu module 758c2ecf20Sopenharmony_ci * 768c2ecf20Sopenharmony_ci * @ctx: pointer to the context structure 778c2ecf20Sopenharmony_ci * 788c2ecf20Sopenharmony_ci * This function does the following: 798c2ecf20Sopenharmony_ci * - Free any pgts which were not freed yet 808c2ecf20Sopenharmony_ci * - Free the mutex 818c2ecf20Sopenharmony_ci * - Free DRAM default page mapping hops 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_civoid hl_mmu_ctx_fini(struct hl_ctx *ctx) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct hl_device *hdev = ctx->hdev; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (hdev->mmu_enable) 888c2ecf20Sopenharmony_ci hdev->mmu_func.ctx_fini(ctx); 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci/* 928c2ecf20Sopenharmony_ci * hl_mmu_unmap - unmaps a virtual addr 938c2ecf20Sopenharmony_ci * 948c2ecf20Sopenharmony_ci * @ctx: pointer to the context structure 958c2ecf20Sopenharmony_ci * @virt_addr: virt addr to map from 968c2ecf20Sopenharmony_ci * @page_size: size of the page to unmap 978c2ecf20Sopenharmony_ci * @flush_pte: whether to do a PCI flush 988c2ecf20Sopenharmony_ci * 998c2ecf20Sopenharmony_ci * This function does the following: 1008c2ecf20Sopenharmony_ci * - Check that the virt addr is mapped 1018c2ecf20Sopenharmony_ci * - Unmap the virt addr and frees pgts if possible 1028c2ecf20Sopenharmony_ci * - Returns 0 on success, -EINVAL if the given addr is not mapped 1038c2ecf20Sopenharmony_ci * 1048c2ecf20Sopenharmony_ci * Because this function changes the page tables in the device and because it 1058c2ecf20Sopenharmony_ci * changes the MMU hash, it must be protected by a lock. 1068c2ecf20Sopenharmony_ci * However, because it maps only a single page, the lock should be implemented 1078c2ecf20Sopenharmony_ci * in a higher level in order to protect the entire mapping of the memory area 1088c2ecf20Sopenharmony_ci * 1098c2ecf20Sopenharmony_ci * For optimization reasons PCI flush may be requested once after unmapping of 1108c2ecf20Sopenharmony_ci * large area. 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_ciint hl_mmu_unmap(struct hl_ctx *ctx, u64 virt_addr, u32 page_size, 1138c2ecf20Sopenharmony_ci bool flush_pte) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct hl_device *hdev = ctx->hdev; 1168c2ecf20Sopenharmony_ci struct asic_fixed_properties *prop = &hdev->asic_prop; 1178c2ecf20Sopenharmony_ci struct hl_mmu_properties *mmu_prop; 1188c2ecf20Sopenharmony_ci u64 real_virt_addr; 1198c2ecf20Sopenharmony_ci u32 real_page_size, npages; 1208c2ecf20Sopenharmony_ci int i, rc = 0; 1218c2ecf20Sopenharmony_ci bool is_dram_addr; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if (!hdev->mmu_enable) 1248c2ecf20Sopenharmony_ci return 0; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci is_dram_addr = is_dram_va(hdev, virt_addr); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (is_dram_addr) 1298c2ecf20Sopenharmony_ci mmu_prop = &prop->dmmu; 1308c2ecf20Sopenharmony_ci else if ((page_size % prop->pmmu_huge.page_size) == 0) 1318c2ecf20Sopenharmony_ci mmu_prop = &prop->pmmu_huge; 1328c2ecf20Sopenharmony_ci else 1338c2ecf20Sopenharmony_ci mmu_prop = &prop->pmmu; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* 1368c2ecf20Sopenharmony_ci * The H/W handles mapping of specific page sizes. Hence if the page 1378c2ecf20Sopenharmony_ci * size is bigger, we break it to sub-pages and unmap them separately. 1388c2ecf20Sopenharmony_ci */ 1398c2ecf20Sopenharmony_ci if ((page_size % mmu_prop->page_size) == 0) { 1408c2ecf20Sopenharmony_ci real_page_size = mmu_prop->page_size; 1418c2ecf20Sopenharmony_ci } else { 1428c2ecf20Sopenharmony_ci dev_err(hdev->dev, 1438c2ecf20Sopenharmony_ci "page size of %u is not %uKB aligned, can't unmap\n", 1448c2ecf20Sopenharmony_ci page_size, mmu_prop->page_size >> 10); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return -EFAULT; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci npages = page_size / real_page_size; 1508c2ecf20Sopenharmony_ci real_virt_addr = virt_addr; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci for (i = 0 ; i < npages ; i++) { 1538c2ecf20Sopenharmony_ci rc = hdev->mmu_func.unmap(ctx, real_virt_addr, is_dram_addr); 1548c2ecf20Sopenharmony_ci if (rc) 1558c2ecf20Sopenharmony_ci break; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci real_virt_addr += real_page_size; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (flush_pte) 1618c2ecf20Sopenharmony_ci hdev->mmu_func.flush(ctx); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci return rc; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci/* 1678c2ecf20Sopenharmony_ci * hl_mmu_map - maps a virtual addr to physical addr 1688c2ecf20Sopenharmony_ci * 1698c2ecf20Sopenharmony_ci * @ctx: pointer to the context structure 1708c2ecf20Sopenharmony_ci * @virt_addr: virt addr to map from 1718c2ecf20Sopenharmony_ci * @phys_addr: phys addr to map to 1728c2ecf20Sopenharmony_ci * @page_size: physical page size 1738c2ecf20Sopenharmony_ci * @flush_pte: whether to do a PCI flush 1748c2ecf20Sopenharmony_ci * 1758c2ecf20Sopenharmony_ci * This function does the following: 1768c2ecf20Sopenharmony_ci * - Check that the virt addr is not mapped 1778c2ecf20Sopenharmony_ci * - Allocate pgts as necessary in order to map the virt addr to the phys 1788c2ecf20Sopenharmony_ci * - Returns 0 on success, -EINVAL if addr is already mapped, or -ENOMEM. 1798c2ecf20Sopenharmony_ci * 1808c2ecf20Sopenharmony_ci * Because this function changes the page tables in the device and because it 1818c2ecf20Sopenharmony_ci * changes the MMU hash, it must be protected by a lock. 1828c2ecf20Sopenharmony_ci * However, because it maps only a single page, the lock should be implemented 1838c2ecf20Sopenharmony_ci * in a higher level in order to protect the entire mapping of the memory area 1848c2ecf20Sopenharmony_ci * 1858c2ecf20Sopenharmony_ci * For optimization reasons PCI flush may be requested once after mapping of 1868c2ecf20Sopenharmony_ci * large area. 1878c2ecf20Sopenharmony_ci */ 1888c2ecf20Sopenharmony_ciint hl_mmu_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr, u32 page_size, 1898c2ecf20Sopenharmony_ci bool flush_pte) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci struct hl_device *hdev = ctx->hdev; 1928c2ecf20Sopenharmony_ci struct asic_fixed_properties *prop = &hdev->asic_prop; 1938c2ecf20Sopenharmony_ci struct hl_mmu_properties *mmu_prop; 1948c2ecf20Sopenharmony_ci u64 real_virt_addr, real_phys_addr; 1958c2ecf20Sopenharmony_ci u32 real_page_size, npages; 1968c2ecf20Sopenharmony_ci int i, rc, mapped_cnt = 0; 1978c2ecf20Sopenharmony_ci bool is_dram_addr; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci if (!hdev->mmu_enable) 2008c2ecf20Sopenharmony_ci return 0; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci is_dram_addr = is_dram_va(hdev, virt_addr); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (is_dram_addr) 2058c2ecf20Sopenharmony_ci mmu_prop = &prop->dmmu; 2068c2ecf20Sopenharmony_ci else if ((page_size % prop->pmmu_huge.page_size) == 0) 2078c2ecf20Sopenharmony_ci mmu_prop = &prop->pmmu_huge; 2088c2ecf20Sopenharmony_ci else 2098c2ecf20Sopenharmony_ci mmu_prop = &prop->pmmu; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* 2128c2ecf20Sopenharmony_ci * The H/W handles mapping of specific page sizes. Hence if the page 2138c2ecf20Sopenharmony_ci * size is bigger, we break it to sub-pages and map them separately. 2148c2ecf20Sopenharmony_ci */ 2158c2ecf20Sopenharmony_ci if ((page_size % mmu_prop->page_size) == 0) { 2168c2ecf20Sopenharmony_ci real_page_size = mmu_prop->page_size; 2178c2ecf20Sopenharmony_ci } else { 2188c2ecf20Sopenharmony_ci dev_err(hdev->dev, 2198c2ecf20Sopenharmony_ci "page size of %u is not %uKB aligned, can't unmap\n", 2208c2ecf20Sopenharmony_ci page_size, mmu_prop->page_size >> 10); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return -EFAULT; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci WARN_ONCE((phys_addr & (real_page_size - 1)), 2268c2ecf20Sopenharmony_ci "Mapping 0x%llx with page size of 0x%x is erroneous! Address must be divisible by page size", 2278c2ecf20Sopenharmony_ci phys_addr, real_page_size); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci npages = page_size / real_page_size; 2308c2ecf20Sopenharmony_ci real_virt_addr = virt_addr; 2318c2ecf20Sopenharmony_ci real_phys_addr = phys_addr; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci for (i = 0 ; i < npages ; i++) { 2348c2ecf20Sopenharmony_ci rc = hdev->mmu_func.map(ctx, real_virt_addr, real_phys_addr, 2358c2ecf20Sopenharmony_ci real_page_size, is_dram_addr); 2368c2ecf20Sopenharmony_ci if (rc) 2378c2ecf20Sopenharmony_ci goto err; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci real_virt_addr += real_page_size; 2408c2ecf20Sopenharmony_ci real_phys_addr += real_page_size; 2418c2ecf20Sopenharmony_ci mapped_cnt++; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (flush_pte) 2458c2ecf20Sopenharmony_ci hdev->mmu_func.flush(ctx); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci return 0; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cierr: 2508c2ecf20Sopenharmony_ci real_virt_addr = virt_addr; 2518c2ecf20Sopenharmony_ci for (i = 0 ; i < mapped_cnt ; i++) { 2528c2ecf20Sopenharmony_ci if (hdev->mmu_func.unmap(ctx, real_virt_addr, is_dram_addr)) 2538c2ecf20Sopenharmony_ci dev_warn_ratelimited(hdev->dev, 2548c2ecf20Sopenharmony_ci "failed to unmap va: 0x%llx\n", real_virt_addr); 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci real_virt_addr += real_page_size; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci hdev->mmu_func.flush(ctx); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci return rc; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci/* 2658c2ecf20Sopenharmony_ci * hl_mmu_swap_out - marks all mapping of the given ctx as swapped out 2668c2ecf20Sopenharmony_ci * 2678c2ecf20Sopenharmony_ci * @ctx: pointer to the context structure 2688c2ecf20Sopenharmony_ci * 2698c2ecf20Sopenharmony_ci */ 2708c2ecf20Sopenharmony_civoid hl_mmu_swap_out(struct hl_ctx *ctx) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci struct hl_device *hdev = ctx->hdev; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (hdev->mmu_enable) 2758c2ecf20Sopenharmony_ci hdev->mmu_func.swap_out(ctx); 2768c2ecf20Sopenharmony_ci} 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci/* 2798c2ecf20Sopenharmony_ci * hl_mmu_swap_in - marks all mapping of the given ctx as swapped in 2808c2ecf20Sopenharmony_ci * 2818c2ecf20Sopenharmony_ci * @ctx: pointer to the context structure 2828c2ecf20Sopenharmony_ci * 2838c2ecf20Sopenharmony_ci */ 2848c2ecf20Sopenharmony_civoid hl_mmu_swap_in(struct hl_ctx *ctx) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct hl_device *hdev = ctx->hdev; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci if (hdev->mmu_enable) 2898c2ecf20Sopenharmony_ci hdev->mmu_func.swap_in(ctx); 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ciint hl_mmu_if_set_funcs(struct hl_device *hdev) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci if (!hdev->mmu_enable) 2958c2ecf20Sopenharmony_ci return 0; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci switch (hdev->asic_type) { 2988c2ecf20Sopenharmony_ci case ASIC_GOYA: 2998c2ecf20Sopenharmony_ci case ASIC_GAUDI: 3008c2ecf20Sopenharmony_ci hl_mmu_v1_set_funcs(hdev); 3018c2ecf20Sopenharmony_ci break; 3028c2ecf20Sopenharmony_ci default: 3038c2ecf20Sopenharmony_ci dev_err(hdev->dev, "Unrecognized ASIC type %d\n", 3048c2ecf20Sopenharmony_ci hdev->asic_type); 3058c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci return 0; 3098c2ecf20Sopenharmony_ci} 310