1 /*
2  * osal_cdev.c
3  *
4  * osal driver
5  *
6  * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
7  *
8  * This software is licensed under the terms of the GNU General Public
9  * License version 2, as published by the Free Software Foundation, and
10  * may be copied, distributed, and modified under those terms.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  */
18 #include <linux/cdev.h>
19 #include <linux/device.h>
20 #include <linux/fs.h>
21 #include <linux/version.h>
22 #include "osal_cdev.h"
23 #include "hdf_log.h"
24 #include "osal_file.h"
25 #include "osal_mem.h"
26 #include "osal_uaccess.h"
27 
28 #define HDF_LOG_TAG osal_cdev
29 
30 #define HDF_MAJOR 231
31 #define HDF_MINOR_START 0
32 #define HDF_MAX_CHAR_DEVICES 1024
33 
34 static DEFINE_IDA(hdf_vnode_ids);
35 
36 struct OsalCdev {
37     struct device dev;
38     struct cdev cdev;
39     struct file_operations fops;
40     const struct OsalCdevOps* opsImpl;
41     void* priv;
42 };
43 
StringRfindChar(const char * str,char chr)44 static const char* StringRfindChar(const char* str, char chr)
45 {
46     const char* p = NULL;
47     if (str == NULL) {
48         return NULL;
49     }
50 
51     p = str + strlen(str);
52     while (p >= str) {
53         if (*p == chr) {
54             return p;
55         }
56         p--;
57     }
58 
59     return NULL;
60 }
61 
62 #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
hdfDevnode(struct device * dev,umode_t * mode)63 static char* hdfDevnode(struct device* dev, umode_t* mode)
64 #else
65 static char* hdfDevnode(const struct device* dev, unsigned short* mode)
66 #endif
67 {
68     (void)mode;
69     return kasprintf(GFP_KERNEL, "hdf/%s", dev_name(dev));
70 }
71 
72 static bool g_hdfClassInitted = false;
73 
74 struct class g_hdfClass = {
75     .name = "hdf",
76     .devnode = hdfDevnode,
77 };
78 
79 static dev_t g_hdfDevt = 0;
80 
HdfClassInit(void)81 static int HdfClassInit(void)
82 {
83     int ret;
84 
85     if (g_hdfClassInitted) {
86         return HDF_SUCCESS;
87     }
88 
89     ret = class_register(&g_hdfClass);
90     if (ret) {
91         HDF_LOGE("failed to register hdf class");
92         return ret;
93     }
94     HDF_LOGI("register hdf class success");
95 
96     ret = alloc_chrdev_region(&g_hdfDevt, HDF_MINOR_START, HDF_MAX_CHAR_DEVICES, "hdf");
97     if (ret) {
98         HDF_LOGE("failed to alloc hdf char major");
99         class_unregister(&g_hdfClass);
100         return ret;
101     }
102 
103     g_hdfClassInitted = true;
104     return HDF_SUCCESS;
105 }
106 
HdfVnodeDevRelease(struct device * dev)107 static void HdfVnodeDevRelease(struct device* dev)
108 {
109     (void)dev;
110 }
111 
RegisterDev(struct OsalCdev * cdev,const char * devName)112 static int RegisterDev(struct OsalCdev* cdev, const char* devName)
113 {
114     int devMinor;
115     int ret;
116 
117     ret = HdfClassInit();
118     if (ret != HDF_SUCCESS) {
119         return ret;
120     }
121 
122     devMinor = ida_simple_get(&hdf_vnode_ids, HDF_MINOR_START, HDF_MAX_CHAR_DEVICES, GFP_KERNEL);
123     if (devMinor < 0) {
124         HDF_LOGE("failed to get hdf dev minor");
125         return HDF_DEV_ERR_NO_DEVICE;
126     }
127 
128     dev_set_name(&cdev->dev, "%s", devName);
129     cdev->dev.devt = MKDEV(MAJOR(g_hdfDevt), devMinor);
130     cdev->dev.class = &g_hdfClass;
131     cdev->dev.parent = NULL;
132     cdev->dev.release = HdfVnodeDevRelease;
133     device_initialize(&cdev->dev);
134 
135     cdev_init(&cdev->cdev, &cdev->fops);
136 
137     ret = cdev_add(&cdev->cdev, cdev->dev.devt, 1);
138     if (ret) {
139         ida_simple_remove(&hdf_vnode_ids, devMinor);
140         HDF_LOGE("failed to add hdf cdev(%s)\n", devName);
141         return ret;
142     }
143 
144     ret = device_add(&cdev->dev);
145     if (ret) {
146         HDF_LOGE("device(%s) add failed\n", devName);
147         cdev_del(&cdev->cdev);
148         ida_simple_remove(&hdf_vnode_ids, devMinor);
149         return ret;
150     }
151 
152     HDF_LOGI("add cdev %s success\n", devName);
153     return 0;
154 }
155 
OsalCdevSeek(struct file * filep,loff_t offset,int whence)156 static loff_t OsalCdevSeek(struct file* filep, loff_t offset, int whence)
157 {
158     struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev);
159     return dev->opsImpl->seek(filep, offset, whence);
160 }
161 
OsalCdevRead(struct file * filep,char __user * buf,size_t buflen,loff_t * offset)162 static ssize_t OsalCdevRead(struct file* filep, char __user* buf, size_t buflen, loff_t* offset)
163 {
164     struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev);
165     return dev->opsImpl->read(filep, buf, buflen, offset);
166 }
167 
OsalCdevWrite(struct file * filep,const char __user * buf,size_t buflen,loff_t * offset)168 static ssize_t OsalCdevWrite(struct file* filep, const char __user* buf, size_t buflen, loff_t* offset)
169 {
170     struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev);
171     return dev->opsImpl->write(filep, buf, buflen, offset);
172 }
173 
OsalCdevPoll(struct file * filep,struct poll_table_struct * pollTable)174 static unsigned int OsalCdevPoll(struct file* filep, struct poll_table_struct* pollTable)
175 {
176     struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev);
177     return dev->opsImpl->poll(filep, pollTable);
178 }
179 
OsalCdevIoctl(struct file * filep,unsigned int cmd,unsigned long arg)180 static long OsalCdevIoctl(struct file* filep, unsigned int cmd, unsigned long arg)
181 {
182     struct OsalCdev* dev = container_of(filep->f_inode->i_cdev, struct OsalCdev, cdev);
183     return dev->opsImpl->ioctl(filep, cmd, arg);
184 }
185 
OsalCdevOpen(struct inode * inode,struct file * filep)186 static int OsalCdevOpen(struct inode* inode, struct file* filep)
187 {
188     struct OsalCdev* dev = container_of(inode->i_cdev, struct OsalCdev, cdev);
189     return dev->opsImpl->open(dev, filep);
190 }
191 
OsalCdevRelease(struct inode * inode,struct file * filep)192 static int OsalCdevRelease(struct inode* inode, struct file* filep)
193 {
194     struct OsalCdev* dev = container_of(inode->i_cdev, struct OsalCdev, cdev);
195     return dev->opsImpl->release(dev, filep);
196 }
197 
AssignFileOps(struct file_operations * fops,const struct OsalCdevOps * src)198 static void AssignFileOps(struct file_operations* fops, const struct OsalCdevOps* src)
199 {
200     fops->llseek = src->seek != NULL ? OsalCdevSeek : NULL;
201     fops->read = src->read != NULL ? OsalCdevRead : NULL;
202     fops->write = src->write != NULL ? OsalCdevWrite : NULL;
203     fops->poll = src->poll != NULL ? OsalCdevPoll : NULL;
204     fops->unlocked_ioctl = src->ioctl != NULL ? OsalCdevIoctl : NULL;
205     fops->open = src->open != NULL ? OsalCdevOpen : NULL;
206     fops->release = src->release != NULL ? OsalCdevRelease : NULL;
207 #ifdef CONFIG_COMPAT
208     fops->compat_ioctl = src->ioctl != NULL ? OsalCdevIoctl : NULL;
209 #endif
210 }
211 
OsalAllocCdev(const struct OsalCdevOps * fops)212 struct OsalCdev* OsalAllocCdev(const struct OsalCdevOps* fops)
213 {
214     struct OsalCdev* cdev = OsalMemCalloc(sizeof(struct OsalCdev));
215     if (cdev == NULL) {
216         return NULL;
217     }
218 
219     AssignFileOps(&cdev->fops, fops);
220     cdev->opsImpl = fops;
221 
222     return cdev;
223 }
224 
OsalRegisterCdev(struct OsalCdev * cdev,const char * name,unsigned int mode,void * priv)225 int OsalRegisterCdev(struct OsalCdev* cdev, const char* name, unsigned int mode, void* priv)
226 {
227     const char* lastSlash;
228     int ret;
229     (void)mode;
230     if (cdev == NULL || name == NULL) {
231         return HDF_ERR_INVALID_PARAM;
232     }
233 
234     lastSlash = StringRfindChar(name, '/');
235     ret = RegisterDev(cdev, (lastSlash == NULL) ? name : (lastSlash + 1));
236     if (ret == HDF_SUCCESS) {
237         cdev->priv = priv;
238     }
239 
240     return ret;
241 }
242 
OsalUnregisterCdev(struct OsalCdev * cdev)243 void OsalUnregisterCdev(struct OsalCdev* cdev)
244 {
245     if (cdev == NULL) {
246         return;
247     }
248     device_del(&cdev->dev);
249     cdev_del(&cdev->cdev);
250     ida_simple_remove(&hdf_vnode_ids, MINOR(cdev->dev.devt));
251 }
252 
OsalFreeCdev(struct OsalCdev * cdev)253 void OsalFreeCdev(struct OsalCdev* cdev)
254 {
255     if (cdev != NULL) {
256         OsalMemFree(cdev);
257     }
258 }
259 
OsalGetCdevPriv(struct OsalCdev * cdev)260 void* OsalGetCdevPriv(struct OsalCdev* cdev)
261 {
262     return cdev != NULL ? cdev->priv : NULL;
263 }
264 
OsalSetFilePriv(struct file * filep,void * priv)265 void OsalSetFilePriv(struct file* filep, void* priv)
266 {
267     if (filep != NULL) {
268         filep->private_data = priv;
269     }
270 }
OsalGetFilePriv(struct file * filep)271 void* OsalGetFilePriv(struct file* filep)
272 {
273     return filep != NULL ? filep->private_data : NULL;
274 }
275