/* * Copyright (C) 2021 HiSilicon (Shanghai) Technologies CO., LIMITED. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base.h" static OSAL_LIST_HEAD(himedia_list); static DEFINE_MUTEX(himedia_sem); /* * Assigned numbers, used for dynamic minors */ #define DYNAMIC_MINORS 64 /* like dynamic majors */ static unsigned char g_himedia_minors[DYNAMIC_MINORS / 8]; /* 8: bitmap 1Byte has 8 bit */ static int himedia_open(struct inode *inode, struct file *file) { int minor = iminor(inode); struct himedia_device *c = NULL; int err = -ENODEV; const struct file_operations *old_fops = NULL; const struct file_operations *new_fops = NULL; mutex_lock(&himedia_sem); osal_list_for_each_entry(c, &himedia_list, list) { if (c->minor == (unsigned int)minor) { new_fops = fops_get(c->fops); break; } } if (new_fops == NULL) { mutex_unlock(&himedia_sem); request_module("char-major-%d-%d", HIMEDIA_DEVICE_MAJOR, minor); mutex_lock(&himedia_sem); osal_list_for_each_entry(c, &himedia_list, list) { if (c->minor == (unsigned int)minor) { new_fops = fops_get(c->fops); break; } } if (new_fops == NULL) { goto fail; } } err = 0; old_fops = file->f_op; file->f_op = new_fops; if (file->f_op->open) { file->private_data = c; err = file->f_op->open(inode, file); if (err) { fops_put(file->f_op); file->private_data = NULL; file->f_op = fops_get(old_fops); } } fops_put(old_fops); fail: mutex_unlock(&himedia_sem); return err; } static struct file_operations g_himedia_fops = { .owner = THIS_MODULE, .open = himedia_open, }; /* * himedia_register - register a himedia device * @himedia: device structure * * Register a himedia device with the kernel. If the minor * number is set to %HIMEDIA_DYNAMIC_MINOR a minor number is assigned * and placed in the minor field of the structure. For other cases * the minor number requested is used. * * The structure passed is linked into the kernel and may not be * destroyed until it has been unregistered. * * A zero is returned on success and a negative errno code for * failure. */ int himedia_register(struct himedia_device *himedia) { struct himedia_device *ptmp = NULL; struct himedia_driver *pdrv = NULL; int err; if (himedia == NULL) { return -EBUSY; } mutex_lock(&himedia_sem); /* check if registered */ osal_list_for_each_entry(ptmp, &himedia_list, list) { if (ptmp->minor == himedia->minor) { mutex_unlock(&himedia_sem); return -EBUSY; } } /* check minor */ if (himedia->minor == HIMEDIA_DYNAMIC_MINOR) { int i = DYNAMIC_MINORS; while (--i >= 0) if ((g_himedia_minors[(unsigned int)i >> 3] & /* 3: left shift 3bit to locate the index of char array */ (1 << ((unsigned int)i & 7))) == 0) { /* 7: locate the bit in bitmap */ break; } if (i < 0) { mutex_unlock(&himedia_sem); return -EBUSY; } himedia->minor = i; } if (himedia->minor < DYNAMIC_MINORS) { g_himedia_minors[himedia->minor >> 3] |= 1 << (himedia->minor & 7); /* 3, 7: bitmap write set bit 1 */ } /* device register */ err = himedia_device_register(himedia); if (err < 0) { g_himedia_minors[himedia->minor >> 3] &= ~(1 << (himedia->minor & 7)); /* 3, 7: bitmap write set bit 0 */ goto out; } /* driver register */ pdrv = himedia_driver_register(himedia->devfs_name, himedia->owner, himedia->drvops); if (IS_ERR(pdrv)) { himedia_device_unregister(himedia); g_himedia_minors[himedia->minor >> 3] &= ~(1 << (himedia->minor & 7)); /* 3, 7: bitmap write set bit 0 */ err = PTR_ERR(pdrv); goto out; } himedia->driver = pdrv; /* * Add it to the front, so that later devices can "override" * earlier defaults */ osal_list_add(&himedia->list, &himedia_list); out: mutex_unlock(&himedia_sem); return err; } EXPORT_SYMBOL(himedia_register); /* * himedia_unregister - unregister a himedia device * @himedia: device to unregister * * Unregister a himedia device that was previously * successfully registered with himedia_register(). Success * is indicated by a zero return, a negative errno code * indicates an error. */ int himedia_unregister(struct himedia_device *himedia) { struct himedia_device *ptmp = NULL; struct himedia_device *_ptmp = NULL; if (himedia == NULL) { return -EINVAL; } if (osal_list_empty(&himedia->list)) { return -EINVAL; } mutex_lock(&himedia_sem); osal_list_for_each_entry_safe(ptmp, _ptmp, &himedia_list, list) { /* if found, unregister device & driver */ if (ptmp->minor == himedia->minor) { osal_list_del(&himedia->list); himedia_driver_unregister(himedia->driver); himedia->driver = NULL; himedia_device_unregister(himedia); g_himedia_minors[himedia->minor >> 3] &= ~(1 << (himedia->minor & 7)); /* 3, 7: bitmap write set bit 0 */ break; } } mutex_unlock(&himedia_sem); return 0; } EXPORT_SYMBOL(himedia_unregister); int himedia_init(void) { int ret; ret = himedia_bus_init(); if (ret) { goto err0; } ret = -EIO; if (register_chrdev(HIMEDIA_DEVICE_MAJOR, "himedia", &g_himedia_fops)) { goto err1; } printk("Module himedia: init ok\n"); return 0; err1: himedia_bus_exit(); err0: return ret; } void himedia_exit(void) { if (!osal_list_empty(&himedia_list)) { printk("!!! Module himedia: sub module in list\n"); return; } unregister_chrdev(HIMEDIA_DEVICE_MAJOR, "himedia"); himedia_bus_exit(); printk("!!! Module himedia: exit ok\n"); }