* 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 <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#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;
}