diff --git a/Documentation/devicetree/bindings/arm/memory_group_manager.txt b/Documentation/devicetree/bindings/arm/memory_group_manager.txt new file mode 100644 index 000000000000..fda8f001dafb --- /dev/null +++ b/Documentation/devicetree/bindings/arm/memory_group_manager.txt @@ -0,0 +1,49 @@ +# +# (C) COPYRIGHT 2019 ARM Limited. All rights reserved. +# +# This program is free software and is provided to you under the terms of the +# GNU General Public License version 2 as published by the Free Software +# Foundation, and any use by you of this program is subject to the terms +# of such GNU licence. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, you can access it online at +# http://www.gnu.org/licenses/gpl-2.0.html. +# +# SPDX-License-Identifier: GPL-2.0 +# +# + +* Arm memory group manager for Mali GPU device drivers + +Required properties: + +- compatible: Must be "arm,physical-memory-group-manager" + +An example node: + + gpu_physical_memory_group_manager: physical-memory-group-manager { + compatible = "arm,physical-memory-group-manager"; + }; + +It must be referenced by the GPU as well, see physical-memory-group-manager: + + gpu: gpu@0x6e000000 { + compatible = "arm,mali-midgard"; + reg = <0x0 0x6e000000 0x0 0x200000>; + interrupts = <0 168 4>, <0 168 4>, <0 168 4>; + interrupt-names = "JOB", "MMU", "GPU"; + clocks = <&scpi_dvfs 2>; + clock-names = "clk_mali"; + system-coherency = <31>; + physical-memory-group-manager = <&gpu_physical_memory_group_manager>; + operating-points = < + /* KHz uV */ + 50000 820000 + >; + }; diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 3e63a900b330..98f129a731e4 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -171,6 +171,8 @@ config SOC_BUS source "drivers/base/regmap/Kconfig" +source "drivers/base/memory_group_manager/Kconfig" + config DMA_SHARED_BUFFER bool default n diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 704f44295810..4a7b6c6b1d7b 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_MODULES) += module.o endif obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o obj-$(CONFIG_REGMAP) += regmap/ +obj-$(CONFIG_MALI_MEMORY_GROUP_MANAGER) += memory_group_manager/ obj-$(CONFIG_SOC_BUS) += soc.o obj-$(CONFIG_PINCTRL) += pinctrl.o obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o diff --git a/drivers/base/memory_group_manager/Kbuild b/drivers/base/memory_group_manager/Kbuild new file mode 100644 index 000000000000..a049bed07431 --- /dev/null +++ b/drivers/base/memory_group_manager/Kbuild @@ -0,0 +1,22 @@ +# +# (C) COPYRIGHT 2019 ARM Limited. All rights reserved. +# +# This program is free software and is provided to you under the terms of the +# GNU General Public License version 2 as published by the Free Software +# Foundation, and any use by you of this program is subject to the terms +# of such GNU licence. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, you can access it online at +# http://www.gnu.org/licenses/gpl-2.0.html. +# +# SPDX-License-Identifier: GPL-2.0 +# +# + +obj-$(CONFIG_MALI_MEMORY_GROUP_MANAGER) := memory_group_manager.o \ No newline at end of file diff --git a/drivers/base/memory_group_manager/Kconfig b/drivers/base/memory_group_manager/Kconfig new file mode 100644 index 000000000000..da464ec68abe --- /dev/null +++ b/drivers/base/memory_group_manager/Kconfig @@ -0,0 +1,28 @@ +# +# (C) COPYRIGHT 2019 ARM Limited. All rights reserved. +# +# This program is free software and is provided to you under the terms of the +# GNU General Public License version 2 as published by the Free Software +# Foundation, and any use by you of this program is subject to the terms +# of such GNU licence. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, you can access it online at +# http://www.gnu.org/licenses/gpl-2.0.html. +# +# SPDX-License-Identifier: GPL-2.0 +# +# + + +config MALI_MEMORY_GROUP_MANAGER + tristate "MALI_MEMORY_GROUP_MANAGER" + help + This option enables an example implementation of a memory group manager + for allocation and release of pages for memory pools managed by Mali GPU + device drivers. diff --git a/drivers/base/memory_group_manager/Makefile b/drivers/base/memory_group_manager/Makefile new file mode 100644 index 000000000000..a5bceae12384 --- /dev/null +++ b/drivers/base/memory_group_manager/Makefile @@ -0,0 +1,35 @@ +# +# (C) COPYRIGHT 2019 ARM Limited. All rights reserved. +# +# This program is free software and is provided to you under the terms of the +# GNU General Public License version 2 as published by the Free Software +# Foundation, and any use by you of this program is subject to the terms +# of such GNU licence. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, you can access it online at +# http://www.gnu.org/licenses/gpl-2.0.html. +# +# SPDX-License-Identifier: GPL-2.0 +# +# + +# linux build system bootstrap for out-of-tree module + +# default to building for the host +ARCH ?= $(shell uname -m) + +ifeq ($(KDIR),) +$(error Must specify KDIR to point to the kernel to target)) +endif + +all: + $(MAKE) ARCH=$(ARCH) -C $(KDIR) M=$(CURDIR) EXTRA_CFLAGS="-I$(CURDIR)/../../../include" modules CONFIG_MALI_MEMORY_GROUP_MANAGER=m + +clean: + $(MAKE) ARCH=$(ARCH) -C $(KDIR) M=$(CURDIR) clean diff --git a/drivers/base/memory_group_manager/build.bp b/drivers/base/memory_group_manager/build.bp new file mode 100644 index 000000000000..04dbfd3a51de --- /dev/null +++ b/drivers/base/memory_group_manager/build.bp @@ -0,0 +1,22 @@ +/* + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +bob_kernel_module { + name: "memory_group_manager", + srcs: [ + "Kbuild", + "memory_group_manager.c", + ], + kbuild_options: ["CONFIG_MALI_MEMORY_GROUP_MANAGER=m"], + defaults: ["kernel_defaults"], +} diff --git a/drivers/base/memory_group_manager/memory_group_manager.c b/drivers/base/memory_group_manager/memory_group_manager.c new file mode 100644 index 000000000000..cbe7f0a775a1 --- /dev/null +++ b/drivers/base/memory_group_manager/memory_group_manager.c @@ -0,0 +1,499 @@ +/* + * + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can access it online at + * http://www.gnu.org/licenses/gpl-2.0.html. + * + * SPDX-License-Identifier: GPL-2.0 + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DEBUG_FS +#include +#endif +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)) +#undef DEFINE_SIMPLE_ATTRIBUTE +#define DEFINE_SIMPLE_ATTRIBUTE DEFINE_DEBUGFS_ATTRIBUTE +#define debugfs_create_file debugfs_create_file_unsafe +#endif + +#if (KERNEL_VERSION(4, 20, 0) > LINUX_VERSION_CODE) +static inline vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, + unsigned long addr, unsigned long pfn, pgprot_t pgprot) +{ + int err; + +#if ((KERNEL_VERSION(4, 4, 147) >= LINUX_VERSION_CODE) || \ + ((KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE) && \ + (KERNEL_VERSION(4, 5, 0) <= LINUX_VERSION_CODE))) + if (pgprot_val(pgprot) != pgprot_val(vma->vm_page_prot)) + return VM_FAULT_SIGBUS; + + err = vm_insert_pfn(vma, addr, pfn); +#else + err = vm_insert_pfn_prot(vma, addr, pfn, pgprot); +#endif + + if (unlikely(err == -ENOMEM)) + return VM_FAULT_OOM; + if (unlikely(err < 0 && err != -EBUSY)) + return VM_FAULT_SIGBUS; + + return VM_FAULT_NOPAGE; +} +#endif + +#define IMPORTED_MEMORY_ID (MEMORY_GROUP_MANAGER_NR_GROUPS - 1) + +/** + * struct mgm_group - Structure to keep track of the number of allocated + * pages per group + * + * @size: The number of allocated small(4KB) pages + * @lp_size: The number of allocated large(2MB) pages + * @insert_pfn: The number of calls to map pages for CPU access. + * @update_gpu_pte: The number of calls to update GPU page table entries. + * + * This structure allows page allocation information to be displayed via + * debugfs. Display is organized per group with small and large sized pages. + */ +struct mgm_group { + size_t size; + size_t lp_size; + size_t insert_pfn; + size_t update_gpu_pte; +}; + +/** + * struct mgm_groups - Structure for groups of memory group manager + * + * @groups: To keep track of the number of allocated pages of all groups + * @dev: device attached + * @mgm_debugfs_root: debugfs root directory of memory group manager + * + * This structure allows page allocation information to be displayed via + * debugfs. Display is organized per group with small and large sized pages. + */ +struct mgm_groups { + struct mgm_group groups[MEMORY_GROUP_MANAGER_NR_GROUPS]; + struct device *dev; +#ifdef CONFIG_DEBUG_FS + struct dentry *mgm_debugfs_root; +#endif +}; + +#ifdef CONFIG_DEBUG_FS + +static int mgm_size_get(void *data, u64 *val) +{ + struct mgm_group *group = data; + + *val = group->size; + + return 0; +} + +static int mgm_lp_size_get(void *data, u64 *val) +{ + struct mgm_group *group = data; + + *val = group->lp_size; + + return 0; +} + +static int mgm_insert_pfn_get(void *data, u64 *val) +{ + struct mgm_group *group = data; + + *val = group->insert_pfn; + + return 0; +} + +static int mgm_update_gpu_pte_get(void *data, u64 *val) +{ + struct mgm_group *group = data; + + *val = group->update_gpu_pte; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_mgm_size, mgm_size_get, NULL, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_mgm_lp_size, mgm_lp_size_get, NULL, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(fops_mgm_insert_pfn, mgm_insert_pfn_get, NULL, + "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(fops_mgm_update_gpu_pte, mgm_update_gpu_pte_get, NULL, + "%llu\n"); + +static void mgm_term_debugfs(struct mgm_groups *data) +{ + debugfs_remove_recursive(data->mgm_debugfs_root); +} + +#define MGM_DEBUGFS_GROUP_NAME_MAX 10 +static int mgm_initialize_debugfs(struct mgm_groups *mgm_data) +{ + int i; + struct dentry *e, *g; + char debugfs_group_name[MGM_DEBUGFS_GROUP_NAME_MAX]; + + /* + * Create root directory of memory-group-manager + */ + mgm_data->mgm_debugfs_root = + debugfs_create_dir("physical-memory-group-manager", NULL); + if (IS_ERR(mgm_data->mgm_debugfs_root)) { + dev_err(mgm_data->dev, "fail to create debugfs root directory\n"); + return -ENODEV; + } + + /* + * Create debugfs files per group + */ + for (i = 0; i < MEMORY_GROUP_MANAGER_NR_GROUPS; i++) { + scnprintf(debugfs_group_name, MGM_DEBUGFS_GROUP_NAME_MAX, + "group_%d", i); + g = debugfs_create_dir(debugfs_group_name, + mgm_data->mgm_debugfs_root); + if (IS_ERR(g)) { + dev_err(mgm_data->dev, "fail to create group[%d]\n", i); + goto remove_debugfs; + } + + e = debugfs_create_file("size", 0444, g, &mgm_data->groups[i], + &fops_mgm_size); + if (IS_ERR(e)) { + dev_err(mgm_data->dev, "fail to create size[%d]\n", i); + goto remove_debugfs; + } + + e = debugfs_create_file("lp_size", 0444, g, + &mgm_data->groups[i], &fops_mgm_lp_size); + if (IS_ERR(e)) { + dev_err(mgm_data->dev, + "fail to create lp_size[%d]\n", i); + goto remove_debugfs; + } + + e = debugfs_create_file("insert_pfn", 0444, g, + &mgm_data->groups[i], &fops_mgm_insert_pfn); + if (IS_ERR(e)) { + dev_err(mgm_data->dev, + "fail to create insert_pfn[%d]\n", i); + goto remove_debugfs; + } + + e = debugfs_create_file("update_gpu_pte", 0444, g, + &mgm_data->groups[i], &fops_mgm_update_gpu_pte); + if (IS_ERR(e)) { + dev_err(mgm_data->dev, + "fail to create update_gpu_pte[%d]\n", i); + goto remove_debugfs; + } + } + + return 0; + +remove_debugfs: + mgm_term_debugfs(mgm_data); + return -ENODEV; +} + +#else + +static void mgm_term_debugfs(struct mgm_groups *data) +{ +} + +static int mgm_initialize_debugfs(struct mgm_groups *mgm_data) +{ + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +#define ORDER_SMALL_PAGE 0 +#define ORDER_LARGE_PAGE 9 +static void update_size(struct memory_group_manager_device *mgm_dev, int + group_id, int order, bool alloc) +{ + struct mgm_groups *data = mgm_dev->data; + + switch (order) { + case ORDER_SMALL_PAGE: + if (alloc) + data->groups[group_id].size++; + else { + WARN_ON(data->groups[group_id].size == 0); + data->groups[group_id].size--; + } + break; + + case ORDER_LARGE_PAGE: + if (alloc) + data->groups[group_id].lp_size++; + else { + WARN_ON(data->groups[group_id].lp_size == 0); + data->groups[group_id].lp_size--; + } + break; + + default: + dev_err(data->dev, "Unknown order(%d)\n", order); + break; + } +} + +static struct page *example_mgm_alloc_page( + struct memory_group_manager_device *mgm_dev, int group_id, + gfp_t gfp_mask, unsigned int order) +{ + struct mgm_groups *const data = mgm_dev->data; + struct page *p; + + dev_dbg(data->dev, "%s(mgm_dev=%p, group_id=%d gfp_mask=0x%x order=%u\n", + __func__, (void *)mgm_dev, group_id, gfp_mask, order); + + if (WARN_ON(group_id < 0) || + WARN_ON(group_id >= MEMORY_GROUP_MANAGER_NR_GROUPS)) + return NULL; + + p = alloc_pages(gfp_mask, order); + + if (p) { + update_size(mgm_dev, group_id, order, true); + } else { + struct mgm_groups *data = mgm_dev->data; + + dev_err(data->dev, "alloc_pages failed\n"); + } + + return p; +} + +static void example_mgm_free_page( + struct memory_group_manager_device *mgm_dev, int group_id, + struct page *page, unsigned int order) +{ + struct mgm_groups *const data = mgm_dev->data; + + dev_dbg(data->dev, "%s(mgm_dev=%p, group_id=%d page=%p order=%u\n", + __func__, (void *)mgm_dev, group_id, (void *)page, order); + + if (WARN_ON(group_id < 0) || + WARN_ON(group_id >= MEMORY_GROUP_MANAGER_NR_GROUPS)) + return; + + __free_pages(page, order); + + update_size(mgm_dev, group_id, order, false); +} + +static int example_mgm_get_import_memory_id( + struct memory_group_manager_device *mgm_dev, + struct memory_group_manager_import_data *import_data) +{ + struct mgm_groups *const data = mgm_dev->data; + + dev_dbg(data->dev, "%s(mgm_dev=%p, import_data=%p (type=%d)\n", + __func__, (void *)mgm_dev, (void *)import_data, + (int)import_data->type); + + if (!WARN_ON(!import_data)) { + WARN_ON(!import_data->u.dma_buf); + + WARN_ON(import_data->type != + MEMORY_GROUP_MANAGER_IMPORT_TYPE_DMA_BUF); + } + + return IMPORTED_MEMORY_ID; +} + +static u64 example_mgm_update_gpu_pte( + struct memory_group_manager_device *const mgm_dev, int const group_id, + int const mmu_level, u64 pte) +{ + struct mgm_groups *const data = mgm_dev->data; + const u32 pbha_bit_pos = 59; /* bits 62:59 */ + const u32 pbha_bit_mask = 0xf; /* 4-bit */ + + dev_dbg(data->dev, + "%s(mgm_dev=%p, group_id=%d, mmu_level=%d, pte=0x%llx)\n", + __func__, (void *)mgm_dev, group_id, mmu_level, pte); + + if (WARN_ON(group_id < 0) || + WARN_ON(group_id >= MEMORY_GROUP_MANAGER_NR_GROUPS)) + return pte; + + pte |= ((u64)group_id & pbha_bit_mask) << pbha_bit_pos; + + data->groups[group_id].update_gpu_pte++; + + return pte; +} + +static vm_fault_t example_mgm_vmf_insert_pfn_prot( + struct memory_group_manager_device *const mgm_dev, int const group_id, + struct vm_area_struct *const vma, unsigned long const addr, + unsigned long const pfn, pgprot_t const prot) +{ + struct mgm_groups *const data = mgm_dev->data; + vm_fault_t fault; + + dev_dbg(data->dev, + "%s(mgm_dev=%p, group_id=%d, vma=%p, addr=0x%lx, pfn=0x%lx, prot=0x%llx)\n", + __func__, (void *)mgm_dev, group_id, (void *)vma, addr, pfn, + (unsigned long long int) pgprot_val(prot)); + + if (WARN_ON(group_id < 0) || + WARN_ON(group_id >= MEMORY_GROUP_MANAGER_NR_GROUPS)) + return VM_FAULT_SIGBUS; + + fault = vmf_insert_pfn_prot(vma, addr, pfn, prot); + + if (fault == VM_FAULT_NOPAGE) + data->groups[group_id].insert_pfn++; + else + dev_err(data->dev, "vmf_insert_pfn_prot failed\n"); + + return fault; +} + +static int mgm_initialize_data(struct mgm_groups *mgm_data) +{ + int i; + + for (i = 0; i < MEMORY_GROUP_MANAGER_NR_GROUPS; i++) { + mgm_data->groups[i].size = 0; + mgm_data->groups[i].lp_size = 0; + mgm_data->groups[i].insert_pfn = 0; + mgm_data->groups[i].update_gpu_pte = 0; + } + + return mgm_initialize_debugfs(mgm_data); +} + +static void mgm_term_data(struct mgm_groups *data) +{ + int i; + + for (i = 0; i < MEMORY_GROUP_MANAGER_NR_GROUPS; i++) { + if (data->groups[i].size != 0) + dev_warn(data->dev, + "%zu 0-order pages in group(%d) leaked\n", + data->groups[i].size, i); + if (data->groups[i].lp_size != 0) + dev_warn(data->dev, + "%zu 9 order pages in group(%d) leaked\n", + data->groups[i].lp_size, i); + } + + mgm_term_debugfs(data); +} + +static int memory_group_manager_probe(struct platform_device *pdev) +{ + struct memory_group_manager_device *mgm_dev; + struct mgm_groups *mgm_data; + + mgm_dev = kzalloc(sizeof(*mgm_dev), GFP_KERNEL); + if (!mgm_dev) + return -ENOMEM; + + mgm_dev->owner = THIS_MODULE; + mgm_dev->ops.mgm_alloc_page = example_mgm_alloc_page; + mgm_dev->ops.mgm_free_page = example_mgm_free_page; + mgm_dev->ops.mgm_get_import_memory_id = + example_mgm_get_import_memory_id; + mgm_dev->ops.mgm_vmf_insert_pfn_prot = example_mgm_vmf_insert_pfn_prot; + mgm_dev->ops.mgm_update_gpu_pte = example_mgm_update_gpu_pte; + + mgm_data = kzalloc(sizeof(*mgm_data), GFP_KERNEL); + if (!mgm_data) { + kfree(mgm_dev); + return -ENOMEM; + } + + mgm_dev->data = mgm_data; + mgm_data->dev = &pdev->dev; + + if (mgm_initialize_data(mgm_data)) { + kfree(mgm_data); + kfree(mgm_dev); + return -ENOENT; + } + + platform_set_drvdata(pdev, mgm_dev); + dev_info(&pdev->dev, "Memory group manager probed successfully\n"); + + return 0; +} + +static int memory_group_manager_remove(struct platform_device *pdev) +{ + struct memory_group_manager_device *mgm_dev = + platform_get_drvdata(pdev); + struct mgm_groups *mgm_data = mgm_dev->data; + + mgm_term_data(mgm_data); + kfree(mgm_data); + + kfree(mgm_dev); + + dev_info(&pdev->dev, "Memory group manager removed successfully\n"); + + return 0; +} + +static const struct of_device_id memory_group_manager_dt_ids[] = { + { .compatible = "arm,physical-memory-group-manager" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, memory_group_manager_dt_ids); + +static struct platform_driver memory_group_manager_driver = { + .probe = memory_group_manager_probe, + .remove = memory_group_manager_remove, + .driver = { + .name = "physical-memory-group-manager", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(memory_group_manager_dt_ids), + /* + * Prevent the mgm_dev from being unbound and freed, as other's + * may have pointers to it and would get confused, or crash, if + * it suddenly disappear. + */ + .suppress_bind_attrs = true, + } +}; + +module_platform_driver(memory_group_manager_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("ARM Ltd."); +MODULE_VERSION("1.0"); diff --git a/include/linux/memory_group_manager.h b/include/linux/memory_group_manager.h new file mode 100644 index 000000000000..b1ac253d9e15 --- /dev/null +++ b/include/linux/memory_group_manager.h @@ -0,0 +1,198 @@ +/* + * + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can access it online at + * http://www.gnu.org/licenses/gpl-2.0.html. + * + * SPDX-License-Identifier: GPL-2.0 + * + */ + +#ifndef _MEMORY_GROUP_MANAGER_H_ +#define _MEMORY_GROUP_MANAGER_H_ + +#include +#include +#include + +#if (KERNEL_VERSION(4, 17, 0) > LINUX_VERSION_CODE) +typedef int vm_fault_t; +#endif + +#define MEMORY_GROUP_MANAGER_NR_GROUPS (16) + +struct memory_group_manager_device; +struct memory_group_manager_import_data; + +/** + * struct memory_group_manager_ops - Callbacks for memory group manager + * operations + * + * @mgm_alloc_page: Callback to allocate physical memory in a group + * @mgm_free_page: Callback to free physical memory in a group + * @mgm_get_import_memory_id: Callback to get the group ID for imported memory + * @mgm_update_gpu_pte: Callback to modify a GPU page table entry + * @mgm_vmf_insert_pfn_prot: Callback to map a physical memory page for the CPU + */ +struct memory_group_manager_ops { + /** + * mgm_alloc_page - Allocate a physical memory page in a group + * + * @mgm_dev: The memory group manager through which the request is + * being made. + * @group_id: A physical memory group ID. The meaning of this is defined + * by the systems integrator. Its valid range is + * 0 .. MEMORY_GROUP_MANAGER_NR_GROUPS-1. + * @gfp_mask: Bitmask of Get Free Page flags affecting allocator + * behavior. + * @order: Page order for physical page size (order=0 means 4 KiB, + * order=9 means 2 MiB). + * + * Return: Pointer to allocated page, or NULL if allocation failed. + */ + struct page *(*mgm_alloc_page)( + struct memory_group_manager_device *mgm_dev, int group_id, + gfp_t gfp_mask, unsigned int order); + + /** + * mgm_free_page - Free a physical memory page in a group + * + * @mgm_dev: The memory group manager through which the request + * is being made. + * @group_id: A physical memory group ID. The meaning of this is + * defined by the systems integrator. Its valid range is + * 0 .. MEMORY_GROUP_MANAGER_NR_GROUPS-1. + * @page: Address of the struct associated with a page of physical + * memory that was allocated by calling the mgm_alloc_page + * method of the same memory pool with the same values of + * @group_id and @order. + * @order: Page order for physical page size (order=0 means 4 KiB, + * order=9 means 2 MiB). + */ + void (*mgm_free_page)( + struct memory_group_manager_device *mgm_dev, int group_id, + struct page *page, unsigned int order); + + /** + * mgm_get_import_memory_id - Get the physical memory group ID for the + * imported memory + * + * @mgm_dev: The memory group manager through which the request + * is being made. + * @import_data: Pointer to the data which describes imported memory. + * + * Note that provision of this call back is optional, where it is not + * provided this call back pointer must be set to NULL to indicate it + * is not in use. + * + * Return: The memory group ID to use when mapping pages from this + * imported memory. + */ + int (*mgm_get_import_memory_id)( + struct memory_group_manager_device *mgm_dev, + struct memory_group_manager_import_data *import_data); + + /** + * mgm_update_gpu_pte - Modify a GPU page table entry for a memory group + * + * @mgm_dev: The memory group manager through which the request + * is being made. + * @group_id: A physical memory group ID. The meaning of this is + * defined by the systems integrator. Its valid range is + * 0 .. MEMORY_GROUP_MANAGER_NR_GROUPS-1. + * @mmu_level: The level of the page table entry in @ate. + * @pte: The page table entry to modify, in LPAE or AArch64 format + * (depending on the driver's configuration). This should be + * decoded to determine the physical address and any other + * properties of the mapping the manager requires. + * + * This function allows the memory group manager to modify a GPU page + * table entry before it is stored by the kbase module (controller + * driver). It may set certain bits in the page table entry attributes + * or in the physical address, based on the physical memory group ID. + * + * Return: A modified GPU page table entry to be stored in a page table. + */ + u64 (*mgm_update_gpu_pte)(struct memory_group_manager_device *mgm_dev, + int group_id, int mmu_level, u64 pte); + + /** + * mgm_vmf_insert_pfn_prot - Map a physical page in a group for the CPU + * + * @mgm_dev: The memory group manager through which the request + * is being made. + * @group_id: A physical memory group ID. The meaning of this is + * defined by the systems integrator. Its valid range is + * 0 .. MEMORY_GROUP_MANAGER_NR_GROUPS-1. + * @vma: The virtual memory area to insert the page into. + * @addr: A virtual address (in @vma) to assign to the page. + * @pfn: The kernel Page Frame Number to insert at @addr in @vma. + * @pgprot: Protection flags for the inserted page. + * + * Called from a CPU virtual memory page fault handler. This function + * creates a page table entry from the given parameter values and stores + * it at the appropriate location (unlike mgm_update_gpu_pte, which + * returns a modified entry). + * + * Return: Type of fault that occurred or VM_FAULT_NOPAGE if the page + * table entry was successfully installed. + */ + vm_fault_t (*mgm_vmf_insert_pfn_prot)( + struct memory_group_manager_device *mgm_dev, int group_id, + struct vm_area_struct *vma, unsigned long addr, + unsigned long pfn, pgprot_t pgprot); +}; + +/** + * struct memory_group_manager_device - Device structure for a memory group + * manager + * + * @ops - Callbacks associated with this device + * @data - Pointer to device private data + * + * In order for a systems integrator to provide custom behaviors for memory + * operations performed by the kbase module (controller driver), they must + * provide a platform-specific driver module which implements this interface. + * + * This structure should be registered with the platform device using + * platform_set_drvdata(). + */ +struct memory_group_manager_device { + struct memory_group_manager_ops ops; + void *data; + struct module *owner; +}; + + +enum memory_group_manager_import_type { + MEMORY_GROUP_MANAGER_IMPORT_TYPE_DMA_BUF +}; + +/** + * struct memory_group_manager_import_data - Structure describing the imported + * memory + * + * @type - type of imported memory + * @u - Union describing the imported memory + * + */ +struct memory_group_manager_import_data { + enum memory_group_manager_import_type type; + union { + struct dma_buf *dma_buf; + } u; +}; + +#endif /* _MEMORY_GROUP_MANAGER_H_ */