/* * osal_cdev.c * * osal driver * * Copyright (c) 2020-2021 Huawei Device Co., Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include "osal_cdev.h" #include "hdf_log.h" #include "osal_file.h" #include "osal_mem.h" #include "osal_uaccess.h" #define HDF_LOG_TAG osal_cdev #define HDF_MAJOR 231 #define HDF_MINOR_START 0 #define HDF_MAX_CHAR_DEVICES 1024 static DEFINE_IDA(hdf_vnode_ids); struct OsalCdev { struct device dev; struct cdev cdev; struct file_operations fops; const struct OsalCdevOps* opsImpl; void* priv; }; static const char* StringRfindChar(const char* str, char chr) { const char* p = NULL; if (str == NULL) { return NULL; } p = str + strlen(str); while (p >= str) { if (*p == chr) { return p; } p--; } return NULL; } static char* hdfDevnode(struct device* dev, umode_t* mode) { (void)mode; return kasprintf(GFP_KERNEL, "hdf/%s", dev_name(dev)); } static bool g_hdfClassInitted = false; struct class g_hdfClass = { .name = "hdf", .devnode = hdfDevnode, }; static dev_t g_hdfDevt = 0; static int HdfClassInit(void) { int ret; if (g_hdfClassInitted) { return HDF_SUCCESS; } ret = class_register(&g_hdfClass); if (ret) { HDF_LOGE("failed to register hdf class"); return ret; } HDF_LOGI("register hdf class success"); ret = alloc_chrdev_region(&g_hdfDevt, HDF_MINOR_START, HDF_MAX_CHAR_DEVICES, "hdf"); if (ret) { HDF_LOGE("failed to alloc hdf char major"); class_unregister(&g_hdfClass); return ret; } g_hdfClassInitted = true; return HDF_SUCCESS; } static void HdfVnodeDevRelease(struct device* dev) { (void)dev; } static int RegisterDev(struct OsalCdev* cdev, const char* devName) { int devMinor; int ret; ret = HdfClassInit(); if (ret != HDF_SUCCESS) { return ret; } devMinor = ida_simple_get(&hdf_vnode_ids, HDF_MINOR_START, HDF_MAX_CHAR_DEVICES, GFP_KERNEL); if (devMinor < 0) { HDF_LOGE("failed to get hdf dev minor"); return HDF_DEV_ERR_NO_DEVICE; } dev_set_name(&cdev->dev, "%s", devName); cdev->dev.devt = MKDEV(MAJOR(g_hdfDevt), devMinor); cdev->dev.class = &g_hdfClass; cdev->dev.parent = NULL; cdev->dev.release = HdfVnodeDevRelease; device_initialize(&cdev->dev); cdev_init(&cdev->cdev, &cdev->fops); ret = cdev_add(&cdev->cdev, cdev->dev.devt, 1); if (ret) { ida_simple_remove(&hdf_vnode_ids, devMinor); HDF_LOGE("failed to add hdf cdev(%s)\n", devName); return ret; } ret = device_add(&cdev->dev); if (ret) { HDF_LOGE("device(%s) add failed\n", devName); cdev_del(&cdev->cdev); ida_simple_remove(&hdf_vnode_ids, devMinor); return ret; } HDF_LOGI("add cdev %s success\n", devName); return 0; } static loff_t OsalCdevSeek(struct file* filep, loff_t offset, int whence) { struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->seek(filep, offset, whence); } static ssize_t OsalCdevRead(struct file* filep, char __user* buf, size_t buflen, loff_t* offset) { struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->read(filep, buf, buflen, offset); } static ssize_t OsalCdevWrite(struct file* filep, const char __user* buf, size_t buflen, loff_t* offset) { struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->write(filep, buf, buflen, offset); } static unsigned int OsalCdevPoll(struct file* filep, struct poll_table_struct* pollTable) { struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->poll(filep, pollTable); } static long OsalCdevIoctl(struct file* filep, unsigned int cmd, unsigned long arg) { struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->ioctl(filep, cmd, arg); } static int OsalCdevOpen(struct inode* inode, struct file* filep) { struct OsalCdev* dev = container_of(inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->open(dev, filep); } static int OsalCdevRelease(struct inode* inode, struct file* filep) { struct OsalCdev* dev = container_of(inode->i_cdev, struct OsalCdev, cdev); return dev->opsImpl->release(dev, filep); } static void AssignFileOps(struct file_operations* fops, const struct OsalCdevOps* src) { fops->llseek = src->seek != NULL ? OsalCdevSeek : NULL; fops->read = src->read != NULL ? OsalCdevRead : NULL; fops->write = src->write != NULL ? OsalCdevWrite : NULL; fops->poll = src->poll != NULL ? OsalCdevPoll : NULL; fops->unlocked_ioctl = src->ioctl != NULL ? OsalCdevIoctl : NULL; fops->open = src->open != NULL ? OsalCdevOpen : NULL; fops->release = src->release != NULL ? OsalCdevRelease : NULL; #ifdef CONFIG_COMPAT fops->compat_ioctl = src->ioctl != NULL ? OsalCdevIoctl : NULL; #endif } struct OsalCdev* OsalAllocCdev(const struct OsalCdevOps* fops) { struct OsalCdev* cdev = OsalMemCalloc(sizeof(struct OsalCdev)); if (cdev == NULL) { return NULL; } AssignFileOps(&cdev->fops, fops); cdev->opsImpl = fops; return cdev; } int OsalRegisterCdev(struct OsalCdev* cdev, const char* name, unsigned int mode, void* priv) { const char* lastSlash; int ret; (void)mode; if (cdev == NULL || name == NULL) { return HDF_ERR_INVALID_PARAM; } lastSlash = StringRfindChar(name, '/'); ret = RegisterDev(cdev, (lastSlash == NULL) ? name : (lastSlash + 1)); if (ret == HDF_SUCCESS) { cdev->priv = priv; } return ret; } void OsalUnregisterCdev(struct OsalCdev* cdev) { if (cdev == NULL) { return; } device_del(&cdev->dev); cdev_del(&cdev->cdev); ida_simple_remove(&hdf_vnode_ids, MINOR(cdev->dev.devt)); } void OsalFreeCdev(struct OsalCdev* cdev) { if (cdev != NULL) { OsalMemFree(cdev); } } void* OsalGetCdevPriv(struct OsalCdev* cdev) { return cdev != NULL ? cdev->priv : NULL; } void OsalSetFilePriv(struct file* filep, void* priv) { if (filep != NULL) { filep->private_data = priv; } } void* OsalGetFilePriv(struct file* filep) { return filep != NULL ? filep->private_data : NULL; }