// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FREEMEM_DEV_NAME "ac_freemem" #define RES_IOC_MAGIC 'R' #define RES_IOCTL_CMD_FREE _IO(RES_IOC_MAGIC, 0x00) static struct reserved_mem res_mem; static bool free_memory; static dev_t dev_no; struct aml_free_dev { struct platform_device *pdev; struct class cls; }; #define AML_FREE_NAME "free_reserved" static int __init sample_res_setup(struct reserved_mem *rmem) { phys_addr_t align = PAGE_SIZE; phys_addr_t mask = align - 1; if ((rmem->base & mask) || (rmem->size & mask)) { pr_err("Reserved memory: incorrect alignment of region\n"); return -EINVAL; } res_mem.base = rmem->base; res_mem.size = rmem->size; /*rmem->ops = &rmem_fb_ops;*/ pr_info("Reserved memory: created fb at 0x%pa, size %ld MiB\n", &rmem->base, (unsigned long)rmem->size / SZ_1M); return 0; } RESERVEDMEM_OF_DECLARE(sample_reserved, "amlogic, aml_autocap_memory", sample_res_setup); #ifdef CONFIG_64BIT static void free_reserved_highmem(unsigned long start, unsigned long end) { } #else static void free_reserved_highmem(unsigned long start, unsigned long end) { for (; start < end; ) { free_highmem_page(phys_to_page(start)); start += PAGE_SIZE; } } #endif static void free_reserved_mem(unsigned long start, unsigned long size) { unsigned long end = PAGE_ALIGN(start + size); struct page *page, *epage; page = phys_to_page(start); if (PageHighMem(page)) { free_reserved_highmem(start, end); } else { epage = phys_to_page(end); if (!PageHighMem(epage)) { free_reserved_area(__va(start), __va(end), 0, "free_reserved"); } else { /* reserved area cross zone */ struct zone *zone; unsigned long bound; zone = page_zone(page); bound = zone_end_pfn(zone); free_reserved_area(__va(start), __va(bound << PAGE_SHIFT), 0, "free_reserved"); zone = page_zone(epage); bound = zone->zone_start_pfn; free_reserved_highmem(bound << PAGE_SHIFT, end); } } } static ssize_t free_reserved_show(const struct class *class, const struct class_attribute *attr, char *buf) { if (free_memory == 1) pr_info("Memory is freed at 0x%pa, size %ld MiB\n", &res_mem.base, (unsigned long)res_mem.size / SZ_1M); else pr_info("Memory is reserved at 0x%pa, size %ld MiB\n", &res_mem.base, (unsigned long)res_mem.size / SZ_1M); return 0; } static ssize_t free_reserved_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { bool free; int ret; ret = kstrtobool(buf, &free); if (ret < 0 || !free) { pr_err("Please enter y or Y or 1 to start freeing memory\n"); return count; } if (free_memory == 1) { pr_info("Memory has been freed\n"); return count; } free_reserved_mem(res_mem.base, res_mem.size); free_memory = 1; return count; } static CLASS_ATTR_RW(free_reserved); static struct attribute *aml_free_attrs[] = { &class_attr_free_reserved.attr, NULL }; ATTRIBUTE_GROUPS(aml_free); static int free_mem_mmap(struct file *file, struct vm_area_struct *vma) { int ret = 0; if (vma->vm_end - vma->vm_start > res_mem.size) { pr_err("vma size is larger than reserved memory\n"); return -EINVAL; } ret = remap_pfn_range(vma, vma->vm_start, res_mem.base >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); if (ret != 0) { pr_err("Failed to mmap reserved memory\n"); return -EINVAL; } return 0; } static long free_mem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case RES_IOCTL_CMD_FREE:{ if (free_memory == 1) return 0; free_reserved_mem(res_mem.base, res_mem.size); free_memory = 1; } break; default: return -EINVAL; } return 0; } #ifdef CONFIG_COMPAT static long free_mem_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned long ret; arg = (unsigned long)compat_ptr(arg); ret = free_mem_ioctl(file, cmd, arg); return ret; } #endif static const struct file_operations free_mem_fops = { .owner = THIS_MODULE, #ifdef CONFIG_COMPAT .compat_ioctl = free_mem_compat_ioctl, #endif .unlocked_ioctl = free_mem_ioctl, .mmap = free_mem_mmap, }; static int aml_free_probe(struct platform_device *pdev) { struct aml_free_dev *jdev; int ret; int r; /* kzalloc device */ jdev = devm_kzalloc(&pdev->dev, sizeof(struct aml_free_dev), GFP_KERNEL); if (!jdev) return -ENOMEM; /* set driver data */ jdev->pdev = pdev; platform_set_drvdata(pdev, jdev); /* create class attributes */ jdev->cls.name = AML_FREE_NAME; jdev->cls.class_groups = aml_free_groups; ret = class_register(&jdev->cls); if (ret) { pr_err("couldn't register sysfs class\n"); return ret; } r = register_chrdev(0, FREEMEM_DEV_NAME, &free_mem_fops); if (r < 0) ret = -EPERM; dev_no = r; device_create(&jdev->cls, &pdev->dev, MKDEV(dev_no, 0), NULL, FREEMEM_DEV_NAME); return ret; } static int __exit aml_free_remove(struct platform_device *pdev) { struct aml_free_dev *jdev = platform_get_drvdata(pdev); class_unregister(&jdev->cls); platform_set_drvdata(pdev, NULL); unregister_chrdev(dev_no, FREEMEM_DEV_NAME); return 0; } static const struct of_device_id aml_free_reserved_dt_match[] = { { .compatible = "amlogic, free_reserved", }, {} }; static struct platform_driver aml_free_driver = { .driver = { .name = AML_FREE_NAME, .owner = THIS_MODULE, .of_match_table = aml_free_reserved_dt_match, }, .probe = aml_free_probe, .remove = __exit_p(aml_free_remove), }; static int __init aml_free_reserved_init(void) { if (platform_driver_register(&aml_free_driver)) { pr_err("failed to register driver\n"); return -ENODEV; } return 0; } fs_initcall(aml_free_reserved_init); static void __exit aml_free_reserved_exit(void) { platform_driver_unregister(&aml_free_driver); } module_exit(aml_free_reserved_exit); MODULE_DESCRIPTION("Free reserved memory"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Amlogic, Inc.");