| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2022 MediaTek Inc. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/debugfs.h> |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/proc_fs.h> |
| #include <linux/cdev.h> |
| #include <linux/mm.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/uaccess.h> |
| #include <linux/ioctl.h> |
| #include <linux/device.h> |
| #if IS_ENABLED(CONFIG_OF) |
| #include <linux/of_fdt.h> |
| #include <linux/of.h> |
| #endif |
| #include <linux/atomic.h> |
| #include <asm/setup.h> |
| #include <mt-plat/mtk_devinfo.h> |
| #if IS_ENABLED(CONFIG_MTK_SECURE_EFUSE) |
| #include <trustzone/tz_cross/ta_efuse.h> |
| #endif |
| #include "devinfo.h" |
| #include <linux/printk.h> |
| |
| enum { |
| DEVINFO_UNINIT = 0, |
| DEVINFO_INITIALIZED = 1 |
| } DEVINFO_INIT_STATE; |
| |
| static u32 *g_devinfo_data; |
| static u32 g_devinfo_size; |
| static struct cdev devinfo_cdev; |
| static struct class *devinfo_class; |
| static dev_t devinfo_dev; |
| static struct dentry *devinfo_segment_root; |
| static char devinfo_segment_buff[128]; |
| static atomic_t g_devinfo_init_status = ATOMIC_INIT(DEVINFO_UNINIT); |
| static atomic_t g_devinfo_init_errcnt = ATOMIC_INIT(0); |
| static struct device_node *chosen_node; |
| |
| /***************************************************************************** |
| *FUNCTION DEFINITION |
| *****************************************************************************/ |
| static int devinfo_open(struct inode *inode, struct file *filp); |
| static int devinfo_release(struct inode *inode, struct file *filp); |
| static long devinfo_ioctl(struct file *file, u32 cmd, unsigned long arg); |
| static ssize_t devinfo_segment_read(struct file *filp, char __user *buf, |
| size_t len, loff_t *ppos); |
| static void init_devinfo_exclusive(void); |
| static void devinfo_parse_dt(void); |
| |
| /************************************************************************** |
| *EXTERN FUNCTION |
| **************************************************************************/ |
| u32 devinfo_get_size(void) |
| { |
| return g_devinfo_size; |
| } |
| EXPORT_SYMBOL(devinfo_get_size); |
| |
| u32 devinfo_ready(void) |
| { |
| if (devinfo_get_size() > 0) |
| return 1; |
| return 0; |
| } |
| EXPORT_SYMBOL(devinfo_ready); |
| |
| u32 get_devinfo_with_index(u32 index) |
| { |
| |
| u32 ret = 0; |
| |
| |
| int size = devinfo_get_size(); |
| |
| if (size == 0) { |
| /* Devinfo API users may call this API earlier than devinfo |
| * data is ready from dt. If the earlier API users found, |
| * make the devinfo data init earlier at that time. |
| */ |
| init_devinfo_exclusive(); |
| size = devinfo_get_size(); |
| } |
| ret = g_devinfo_data[index]; |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(get_devinfo_with_index); |
| |
| /************************************************************************** |
| *STATIC FUNCTION |
| **************************************************************************/ |
| |
| static const struct file_operations devinfo_fops = { |
| .open = devinfo_open, |
| .release = devinfo_release, |
| .unlocked_ioctl = devinfo_ioctl, |
| #if IS_ENABLED(CONFIG_COMPAT) |
| .compat_ioctl = devinfo_ioctl, |
| #endif |
| .owner = THIS_MODULE, |
| }; |
| |
| static int devinfo_open(struct inode *inode, struct file *filp) |
| { |
| return 0; |
| } |
| |
| static int devinfo_release(struct inode *inode, struct file *filp) |
| { |
| return 0; |
| } |
| |
| static const struct file_operations devinfo_segment_fops = { |
| .owner = THIS_MODULE, |
| .read = devinfo_segment_read, |
| }; |
| |
| static ssize_t devinfo_segment_read(struct file *filp, char __user *buf, |
| size_t len, loff_t *ppos) |
| { |
| uint efuse_value = 0; |
| efuse_value = get_devinfo_with_index(20); |
| pr_info("efuse idx20:%x", efuse_value); |
| return simple_read_from_buffer(buf, len, ppos, devinfo_segment_buff, |
| strlen(devinfo_segment_buff)); |
| } |
| |
| /************************************************************************** |
| * DEV DRIVER IOCTL |
| **************************************************************************/ |
| static long devinfo_ioctl(struct file *file, u32 cmd, unsigned long arg) |
| { |
| u32 index = 0; |
| int err = 0; |
| int ret = 0; |
| u32 data_read = 0; |
| int size = devinfo_get_size(); |
| |
| /* IOCTL */ |
| if (_IOC_TYPE(cmd) != DEV_IOC_MAGIC) |
| return -ENOTTY; |
| if (_IOC_NR(cmd) > DEV_IOC_MAXNR) |
| return -ENOTTY; |
| if (_IOC_DIR(cmd) & _IOC_READ) |
| err = !access_ok((void __user *)arg, |
| _IOC_SIZE(cmd)); |
| if (_IOC_DIR(cmd) & _IOC_WRITE) |
| err = !access_ok((void __user *)arg, |
| _IOC_SIZE(cmd)); |
| if (err) |
| return -EFAULT; |
| |
| switch (cmd) { |
| /* get dev info data */ |
| case READ_DEV_DATA: |
| if (copy_from_user((void *)&index, (void __user *)arg, |
| sizeof(u32))) |
| return -1; |
| if (index < size) { |
| data_read = get_devinfo_with_index(index); |
| ret = copy_to_user((void __user *)arg, |
| (void *)&(data_read), sizeof(u32)); |
| } else { |
| pr_info("%s Error! Index %d is larger than size %d\n", |
| MODULE_NAME, index, size); |
| return -2; |
| } |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * devinfo_init |
| * |
| * DESCRIPTION: |
| * Init the device driver ! |
| * |
| * PARAMETERS: |
| * None |
| * |
| * RETURNS: |
| * 0 for success |
| * |
| * NOTES: |
| * None |
| * |
| *****************************************************************************/ |
| static int __init devinfo_init(void) |
| { |
| int ret = 0; |
| struct device *device; |
| |
| devinfo_dev = MKDEV(MAJOR_DEV_NUM, 0); |
| pr_info("[%s]init\n", MODULE_NAME); |
| ret = register_chrdev_region(devinfo_dev, 1, DEV_NAME); |
| if (ret) { |
| pr_info("[%s] register device failed, ret:%d\n", |
| MODULE_NAME, ret); |
| return ret; |
| } |
| /*create class*/ |
| devinfo_class = class_create(THIS_MODULE, DEV_NAME); |
| if (IS_ERR(devinfo_class)) { |
| ret = PTR_ERR(devinfo_class); |
| pr_info("[%s] register class failed, ret:%d\n", |
| MODULE_NAME, ret); |
| unregister_chrdev_region(devinfo_dev, 1); |
| return ret; |
| } |
| /* initialize the device structure and register the device */ |
| cdev_init(&devinfo_cdev, &devinfo_fops); |
| devinfo_cdev.owner = THIS_MODULE; |
| |
| ret = cdev_add(&devinfo_cdev, devinfo_dev, 1); |
| if (ret < 0) { |
| pr_info("[%s] could not allocate chrdev for the device, ret:%d\n", |
| MODULE_NAME, ret); |
| class_destroy(devinfo_class); |
| unregister_chrdev_region(devinfo_dev, 1); |
| return ret; |
| } |
| /*create device*/ |
| device = device_create(devinfo_class, NULL, devinfo_dev, NULL, |
| "devmap"); |
| if (IS_ERR(device)) { |
| ret = PTR_ERR(device); |
| pr_info("[%s]device create fail\n", MODULE_NAME); |
| cdev_del(&devinfo_cdev); |
| class_destroy(devinfo_class); |
| unregister_chrdev_region(devinfo_dev, 1); |
| return ret; |
| } |
| |
| devinfo_segment_root = debugfs_create_dir("devinfo", NULL); |
| if (!devinfo_segment_root) |
| return -ENOMEM; |
| |
| if (!debugfs_create_file("segcode", 0444, devinfo_segment_root, NULL, |
| &devinfo_segment_fops)) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static void devinfo_parse_dt(void) |
| { |
| struct devinfo_tag *tags; |
| u32 size = 0; |
| |
| chosen_node = of_find_node_by_path("/chosen"); |
| if (!chosen_node) { |
| chosen_node = of_find_node_by_path("/chosen@0"); |
| if (!chosen_node) { |
| pr_info("chosen node is not found!!\n"); |
| return; |
| } |
| } |
| |
| tags = (struct devinfo_tag *) of_get_property(chosen_node, |
| "atag,devinfo", NULL); |
| if (tags) { |
| size = tags->data_size; |
| g_devinfo_data = kmalloc(sizeof(struct devinfo_tag) + |
| (size * sizeof(u32)), GFP_KERNEL); |
| if (!g_devinfo_data) |
| return; |
| g_devinfo_size = size; |
| |
| WARN_ON(size > 300); /* for size integer too big protection */ |
| |
| memcpy(g_devinfo_data, tags->data, |
| (size * sizeof(u32))); |
| } else { |
| sprintf(devinfo_segment_buff, |
| "segment code=[Fail in parsing DT]\n"); |
| |
| pr_info("'atag,devinfo' is not found\n"); |
| } |
| } |
| |
| static void init_devinfo_exclusive(void) |
| { |
| if (atomic_read(&g_devinfo_init_status) == DEVINFO_INITIALIZED) { |
| atomic_inc(&g_devinfo_init_errcnt); |
| pr_info("%s Already init done earlier. Extra times:%d.\n", |
| MODULE_NAME, atomic_read(&g_devinfo_init_errcnt)); |
| return; |
| } |
| |
| if (atomic_read(&g_devinfo_init_status) == DEVINFO_UNINIT) |
| atomic_set(&g_devinfo_init_status, DEVINFO_INITIALIZED); |
| else |
| return; |
| devinfo_parse_dt(); |
| } |
| |
| /****************************************************************************** |
| * devinfo_exit |
| * |
| * DESCRIPTION: |
| * Free the device driver ! |
| * |
| * PARAMETERS: |
| * None |
| * |
| * RETURNS: |
| * None |
| * |
| * NOTES: |
| * None |
| * |
| *****************************************************************************/ |
| static void __exit devinfo_exit(void) |
| { |
| debugfs_remove_recursive(devinfo_segment_root); |
| cdev_del(&devinfo_cdev); |
| class_destroy(devinfo_class); |
| unregister_chrdev_region(devinfo_dev, 1); |
| } |
| |
| module_init(devinfo_init); |
| module_exit(devinfo_exit); |
| MODULE_LICENSE("GPL"); |
| |
| |