1 /*
2 * Copyright (C) 2021 HiSilicon (Shanghai) Technologies CO., LIMITED.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include <linux/module.h>
20 #include <linux/fs.h>
21 #include <linux/errno.h>
22 #include <linux/kernel.h>
23 #include <linux/major.h>
24 #include <linux/slab.h>
25 #include <linux/mutex.h>
26 #include <linux/proc_fs.h>
27 #include <linux/seq_file.h>
28 #include <linux/stat.h>
29 #include <linux/init.h>
30 #include <linux/device.h>
31 #include <linux/tty.h>
32 #include <linux/kmod.h>
33
34 #include "base.h"
35
36 static OSAL_LIST_HEAD(himedia_list);
37 static DEFINE_MUTEX(himedia_sem);
38
39 /*
40 * Assigned numbers, used for dynamic minors
41 */
42 #define DYNAMIC_MINORS 64 /* like dynamic majors */
43 static unsigned char g_himedia_minors[DYNAMIC_MINORS / 8]; /* 8: bitmap 1Byte has 8 bit */
44
himedia_open(struct inode * inode,struct file * file)45 static int himedia_open(struct inode *inode, struct file *file)
46 {
47 int minor = iminor(inode);
48 struct himedia_device *c = NULL;
49 int err = -ENODEV;
50 const struct file_operations *old_fops = NULL;
51 const struct file_operations *new_fops = NULL;
52
53 mutex_lock(&himedia_sem);
54
55 osal_list_for_each_entry(c, &himedia_list, list) {
56 if (c->minor == (unsigned int)minor) {
57 new_fops = fops_get(c->fops);
58 break;
59 }
60 }
61
62 if (new_fops == NULL) {
63 mutex_unlock(&himedia_sem);
64 request_module("char-major-%d-%d", HIMEDIA_DEVICE_MAJOR, minor);
65 mutex_lock(&himedia_sem);
66
67 osal_list_for_each_entry(c, &himedia_list, list) {
68 if (c->minor == (unsigned int)minor) {
69 new_fops = fops_get(c->fops);
70 break;
71 }
72 }
73
74 if (new_fops == NULL) {
75 goto fail;
76 }
77 }
78
79 err = 0;
80
81 old_fops = file->f_op;
82 file->f_op = new_fops;
83 if (file->f_op->open) {
84 file->private_data = c;
85 err = file->f_op->open(inode, file);
86 if (err) {
87 fops_put(file->f_op);
88 file->private_data = NULL;
89 file->f_op = fops_get(old_fops);
90 }
91 }
92
93 fops_put(old_fops);
94 fail:
95 mutex_unlock(&himedia_sem);
96 return err;
97 }
98
99 static struct file_operations g_himedia_fops = {
100 .owner = THIS_MODULE,
101 .open = himedia_open,
102 };
103
104 /*
105 * himedia_register - register a himedia device
106 * @himedia: device structure
107 *
108 * Register a himedia device with the kernel. If the minor
109 * number is set to %HIMEDIA_DYNAMIC_MINOR a minor number is assigned
110 * and placed in the minor field of the structure. For other cases
111 * the minor number requested is used.
112 *
113 * The structure passed is linked into the kernel and may not be
114 * destroyed until it has been unregistered.
115 *
116 * A zero is returned on success and a negative errno code for
117 * failure.
118 */
himedia_register(struct himedia_device * himedia)119 int himedia_register(struct himedia_device *himedia)
120 {
121 struct himedia_device *ptmp = NULL;
122 struct himedia_driver *pdrv = NULL;
123 int err;
124
125 if (himedia == NULL) {
126 return -EBUSY;
127 }
128 mutex_lock(&himedia_sem);
129
130 /* check if registered */
131 osal_list_for_each_entry(ptmp, &himedia_list, list) {
132 if (ptmp->minor == himedia->minor) {
133 mutex_unlock(&himedia_sem);
134 return -EBUSY;
135 }
136 }
137
138 /* check minor */
139 if (himedia->minor == HIMEDIA_DYNAMIC_MINOR) {
140 int i = DYNAMIC_MINORS;
141
142 while (--i >= 0)
143 if ((g_himedia_minors[(unsigned int)i >> 3] & /* 3: left shift 3bit to locate the index of char array */
144 (1 << ((unsigned int)i & 7))) == 0) { /* 7: locate the bit in bitmap */
145 break;
146 }
147 if (i < 0) {
148 mutex_unlock(&himedia_sem);
149 return -EBUSY;
150 }
151
152 himedia->minor = i;
153 }
154
155 if (himedia->minor < DYNAMIC_MINORS) {
156 g_himedia_minors[himedia->minor >> 3] |= 1 << (himedia->minor & 7); /* 3, 7: bitmap write set bit 1 */
157 }
158
159 /* device register */
160 err = himedia_device_register(himedia);
161 if (err < 0) {
162 g_himedia_minors[himedia->minor >> 3] &= ~(1 << (himedia->minor & 7)); /* 3, 7: bitmap write set bit 0 */
163 goto out;
164 }
165
166 /* driver register */
167 pdrv = himedia_driver_register(himedia->devfs_name, himedia->owner, himedia->drvops);
168 if (IS_ERR(pdrv)) {
169 himedia_device_unregister(himedia);
170
171 g_himedia_minors[himedia->minor >> 3] &= ~(1 << (himedia->minor & 7)); /* 3, 7: bitmap write set bit 0 */
172
173 err = PTR_ERR(pdrv);
174 goto out;
175 }
176
177 himedia->driver = pdrv;
178
179 /*
180 * Add it to the front, so that later devices can "override"
181 * earlier defaults
182 */
183 osal_list_add(&himedia->list, &himedia_list);
184
185 out:
186 mutex_unlock(&himedia_sem);
187 return err;
188 }
189 EXPORT_SYMBOL(himedia_register);
190
191 /*
192 * himedia_unregister - unregister a himedia device
193 * @himedia: device to unregister
194 *
195 * Unregister a himedia device that was previously
196 * successfully registered with himedia_register(). Success
197 * is indicated by a zero return, a negative errno code
198 * indicates an error.
199 */
himedia_unregister(struct himedia_device * himedia)200 int himedia_unregister(struct himedia_device *himedia)
201 {
202 struct himedia_device *ptmp = NULL;
203 struct himedia_device *_ptmp = NULL;
204
205 if (himedia == NULL) {
206 return -EINVAL;
207 }
208
209 if (osal_list_empty(&himedia->list)) {
210 return -EINVAL;
211 }
212
213 mutex_lock(&himedia_sem);
214
215 osal_list_for_each_entry_safe(ptmp, _ptmp, &himedia_list, list) {
216 /* if found, unregister device & driver */
217 if (ptmp->minor == himedia->minor) {
218 osal_list_del(&himedia->list);
219
220 himedia_driver_unregister(himedia->driver);
221
222 himedia->driver = NULL;
223
224 himedia_device_unregister(himedia);
225
226 g_himedia_minors[himedia->minor >> 3] &= ~(1 << (himedia->minor & 7)); /* 3, 7: bitmap write set bit 0 */
227
228 break;
229 }
230 }
231
232 mutex_unlock(&himedia_sem);
233
234 return 0;
235 }
236 EXPORT_SYMBOL(himedia_unregister);
237
himedia_init(void)238 int himedia_init(void)
239 {
240 int ret;
241 ret = himedia_bus_init();
242 if (ret) {
243 goto err0;
244 }
245
246 ret = -EIO;
247
248 if (register_chrdev(HIMEDIA_DEVICE_MAJOR, "himedia", &g_himedia_fops)) {
249 goto err1;
250 }
251
252 printk("Module himedia: init ok\n");
253
254 return 0;
255
256 err1:
257 himedia_bus_exit();
258 err0:
259 return ret;
260 }
261
himedia_exit(void)262 void himedia_exit(void)
263 {
264 if (!osal_list_empty(&himedia_list)) {
265 printk("!!! Module himedia: sub module in list\n");
266 return;
267 }
268
269 unregister_chrdev(HIMEDIA_DEVICE_MAJOR, "himedia");
270
271 himedia_bus_exit();
272
273 printk("!!! Module himedia: exit ok\n");
274 }
275