1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Fieldbus Device Driver Core |
4 | * |
5 | */ |
6 | |
7 | #include <linux/mutex.h> |
8 | #include <linux/module.h> |
9 | #include <linux/device.h> |
10 | #include <linux/idr.h> |
11 | #include <linux/fs.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/poll.h> |
14 | |
15 | /* move to <linux/fieldbus_dev.h> when taking this out of staging */ |
16 | #include "fieldbus_dev.h" |
17 | |
18 | /* Maximum number of fieldbus devices */ |
19 | #define MAX_FIELDBUSES 32 |
20 | |
21 | /* the dev_t structure to store the dynamically allocated fieldbus devices */ |
22 | static dev_t fieldbus_devt; |
23 | static DEFINE_IDA(fieldbus_ida); |
24 | static DEFINE_MUTEX(fieldbus_mtx); |
25 | |
26 | static ssize_t online_show(struct device *dev, struct device_attribute *attr, |
27 | char *buf) |
28 | { |
29 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
30 | |
31 | return sysfs_emit(buf, fmt: "%d\n" , !!fb->online); |
32 | } |
33 | static DEVICE_ATTR_RO(online); |
34 | |
35 | static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, |
36 | char *buf) |
37 | { |
38 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
39 | |
40 | if (!fb->enable_get) |
41 | return -EINVAL; |
42 | return sysfs_emit(buf, fmt: "%d\n" , !!fb->enable_get(fb)); |
43 | } |
44 | |
45 | static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, |
46 | const char *buf, size_t n) |
47 | { |
48 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
49 | bool value; |
50 | int ret; |
51 | |
52 | if (!fb->simple_enable_set) |
53 | return -ENOTSUPP; |
54 | ret = kstrtobool(s: buf, res: &value); |
55 | if (ret) |
56 | return ret; |
57 | ret = fb->simple_enable_set(fb, value); |
58 | if (ret < 0) |
59 | return ret; |
60 | return n; |
61 | } |
62 | static DEVICE_ATTR_RW(enabled); |
63 | |
64 | static ssize_t card_name_show(struct device *dev, struct device_attribute *attr, |
65 | char *buf) |
66 | { |
67 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
68 | |
69 | /* card_name was provided by child driver. */ |
70 | return sysfs_emit(buf, fmt: "%s\n" , fb->card_name); |
71 | } |
72 | static DEVICE_ATTR_RO(card_name); |
73 | |
74 | static ssize_t read_area_size_show(struct device *dev, |
75 | struct device_attribute *attr, char *buf) |
76 | { |
77 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
78 | |
79 | return sysfs_emit(buf, fmt: "%zu\n" , fb->read_area_sz); |
80 | } |
81 | static DEVICE_ATTR_RO(read_area_size); |
82 | |
83 | static ssize_t write_area_size_show(struct device *dev, |
84 | struct device_attribute *attr, char *buf) |
85 | { |
86 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
87 | |
88 | return sysfs_emit(buf, fmt: "%zu\n" , fb->write_area_sz); |
89 | } |
90 | static DEVICE_ATTR_RO(write_area_size); |
91 | |
92 | static ssize_t fieldbus_id_show(struct device *dev, |
93 | struct device_attribute *attr, char *buf) |
94 | { |
95 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
96 | |
97 | return fb->fieldbus_id_get(fb, buf, PAGE_SIZE); |
98 | } |
99 | static DEVICE_ATTR_RO(fieldbus_id); |
100 | |
101 | static ssize_t fieldbus_type_show(struct device *dev, |
102 | struct device_attribute *attr, char *buf) |
103 | { |
104 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
105 | const char *t; |
106 | |
107 | switch (fb->fieldbus_type) { |
108 | case FIELDBUS_DEV_TYPE_PROFINET: |
109 | t = "profinet" ; |
110 | break; |
111 | default: |
112 | t = "unknown" ; |
113 | break; |
114 | } |
115 | |
116 | return sysfs_emit(buf, fmt: "%s\n" , t); |
117 | } |
118 | static DEVICE_ATTR_RO(fieldbus_type); |
119 | |
120 | static struct attribute *fieldbus_attrs[] = { |
121 | &dev_attr_enabled.attr, |
122 | &dev_attr_card_name.attr, |
123 | &dev_attr_fieldbus_id.attr, |
124 | &dev_attr_read_area_size.attr, |
125 | &dev_attr_write_area_size.attr, |
126 | &dev_attr_online.attr, |
127 | &dev_attr_fieldbus_type.attr, |
128 | NULL, |
129 | }; |
130 | |
131 | static umode_t fieldbus_is_visible(struct kobject *kobj, struct attribute *attr, |
132 | int n) |
133 | { |
134 | struct device *dev = kobj_to_dev(kobj); |
135 | struct fieldbus_dev *fb = dev_get_drvdata(dev); |
136 | umode_t mode = attr->mode; |
137 | |
138 | if (attr == &dev_attr_enabled.attr) { |
139 | mode = 0; |
140 | if (fb->enable_get) |
141 | mode |= 0444; |
142 | if (fb->simple_enable_set) |
143 | mode |= 0200; |
144 | } |
145 | |
146 | return mode; |
147 | } |
148 | |
149 | static const struct attribute_group fieldbus_group = { |
150 | .attrs = fieldbus_attrs, |
151 | .is_visible = fieldbus_is_visible, |
152 | }; |
153 | __ATTRIBUTE_GROUPS(fieldbus); |
154 | |
155 | static const struct class fieldbus_class = { |
156 | .name = "fieldbus_dev" , |
157 | .dev_groups = fieldbus_groups, |
158 | }; |
159 | |
160 | struct fb_open_file { |
161 | struct fieldbus_dev *fbdev; |
162 | int dc_event; |
163 | }; |
164 | |
165 | static int fieldbus_open(struct inode *inode, struct file *filp) |
166 | { |
167 | struct fb_open_file *of; |
168 | struct fieldbus_dev *fbdev = container_of(inode->i_cdev, |
169 | struct fieldbus_dev, |
170 | cdev); |
171 | |
172 | of = kzalloc(size: sizeof(*of), GFP_KERNEL); |
173 | if (!of) |
174 | return -ENOMEM; |
175 | of->fbdev = fbdev; |
176 | filp->private_data = of; |
177 | return 0; |
178 | } |
179 | |
180 | static int fieldbus_release(struct inode *node, struct file *filp) |
181 | { |
182 | struct fb_open_file *of = filp->private_data; |
183 | |
184 | kfree(objp: of); |
185 | return 0; |
186 | } |
187 | |
188 | static ssize_t fieldbus_read(struct file *filp, char __user *buf, size_t size, |
189 | loff_t *offset) |
190 | { |
191 | struct fb_open_file *of = filp->private_data; |
192 | struct fieldbus_dev *fbdev = of->fbdev; |
193 | |
194 | of->dc_event = fbdev->dc_event; |
195 | return fbdev->read_area(fbdev, buf, size, offset); |
196 | } |
197 | |
198 | static ssize_t fieldbus_write(struct file *filp, const char __user *buf, |
199 | size_t size, loff_t *offset) |
200 | { |
201 | struct fb_open_file *of = filp->private_data; |
202 | struct fieldbus_dev *fbdev = of->fbdev; |
203 | |
204 | return fbdev->write_area(fbdev, buf, size, offset); |
205 | } |
206 | |
207 | static __poll_t fieldbus_poll(struct file *filp, poll_table *wait) |
208 | { |
209 | struct fb_open_file *of = filp->private_data; |
210 | struct fieldbus_dev *fbdev = of->fbdev; |
211 | __poll_t mask = EPOLLIN | EPOLLRDNORM | EPOLLOUT | EPOLLWRNORM; |
212 | |
213 | poll_wait(filp, wait_address: &fbdev->dc_wq, p: wait); |
214 | /* data changed ? */ |
215 | if (fbdev->dc_event != of->dc_event) |
216 | mask |= EPOLLPRI | EPOLLERR; |
217 | return mask; |
218 | } |
219 | |
220 | static const struct file_operations fieldbus_fops = { |
221 | .open = fieldbus_open, |
222 | .release = fieldbus_release, |
223 | .read = fieldbus_read, |
224 | .write = fieldbus_write, |
225 | .poll = fieldbus_poll, |
226 | .llseek = generic_file_llseek, |
227 | .owner = THIS_MODULE, |
228 | }; |
229 | |
230 | void fieldbus_dev_area_updated(struct fieldbus_dev *fb) |
231 | { |
232 | fb->dc_event++; |
233 | wake_up_all(&fb->dc_wq); |
234 | } |
235 | EXPORT_SYMBOL_GPL(fieldbus_dev_area_updated); |
236 | |
237 | void fieldbus_dev_online_changed(struct fieldbus_dev *fb, bool online) |
238 | { |
239 | fb->online = online; |
240 | kobject_uevent(kobj: &fb->dev->kobj, action: KOBJ_CHANGE); |
241 | } |
242 | EXPORT_SYMBOL_GPL(fieldbus_dev_online_changed); |
243 | |
244 | static void __fieldbus_dev_unregister(struct fieldbus_dev *fb) |
245 | { |
246 | if (!fb) |
247 | return; |
248 | device_destroy(cls: &fieldbus_class, devt: fb->cdev.dev); |
249 | cdev_del(&fb->cdev); |
250 | ida_free(&fieldbus_ida, id: fb->id); |
251 | } |
252 | |
253 | void fieldbus_dev_unregister(struct fieldbus_dev *fb) |
254 | { |
255 | mutex_lock(&fieldbus_mtx); |
256 | __fieldbus_dev_unregister(fb); |
257 | mutex_unlock(lock: &fieldbus_mtx); |
258 | } |
259 | EXPORT_SYMBOL_GPL(fieldbus_dev_unregister); |
260 | |
261 | static int __fieldbus_dev_register(struct fieldbus_dev *fb) |
262 | { |
263 | dev_t devno; |
264 | int err; |
265 | |
266 | if (!fb) |
267 | return -EINVAL; |
268 | if (!fb->read_area || !fb->write_area || !fb->fieldbus_id_get) |
269 | return -EINVAL; |
270 | fb->id = ida_alloc_max(ida: &fieldbus_ida, MAX_FIELDBUSES - 1, GFP_KERNEL); |
271 | if (fb->id < 0) |
272 | return fb->id; |
273 | devno = MKDEV(MAJOR(fieldbus_devt), fb->id); |
274 | init_waitqueue_head(&fb->dc_wq); |
275 | cdev_init(&fb->cdev, &fieldbus_fops); |
276 | err = cdev_add(&fb->cdev, devno, 1); |
277 | if (err) { |
278 | pr_err("fieldbus_dev%d unable to add device %d:%d\n" , |
279 | fb->id, MAJOR(fieldbus_devt), fb->id); |
280 | goto err_cdev; |
281 | } |
282 | fb->dev = device_create(cls: &fieldbus_class, parent: fb->parent, devt: devno, drvdata: fb, |
283 | fmt: "fieldbus_dev%d" , fb->id); |
284 | if (IS_ERR(ptr: fb->dev)) { |
285 | err = PTR_ERR(ptr: fb->dev); |
286 | goto err_dev_create; |
287 | } |
288 | return 0; |
289 | |
290 | err_dev_create: |
291 | cdev_del(&fb->cdev); |
292 | err_cdev: |
293 | ida_free(&fieldbus_ida, id: fb->id); |
294 | return err; |
295 | } |
296 | |
297 | int fieldbus_dev_register(struct fieldbus_dev *fb) |
298 | { |
299 | int err; |
300 | |
301 | mutex_lock(&fieldbus_mtx); |
302 | err = __fieldbus_dev_register(fb); |
303 | mutex_unlock(lock: &fieldbus_mtx); |
304 | |
305 | return err; |
306 | } |
307 | EXPORT_SYMBOL_GPL(fieldbus_dev_register); |
308 | |
309 | static int __init fieldbus_init(void) |
310 | { |
311 | int err; |
312 | |
313 | err = class_register(class: &fieldbus_class); |
314 | if (err < 0) { |
315 | pr_err("fieldbus_dev: could not register class\n" ); |
316 | return err; |
317 | } |
318 | err = alloc_chrdev_region(&fieldbus_devt, 0, |
319 | MAX_FIELDBUSES, "fieldbus_dev" ); |
320 | if (err < 0) { |
321 | pr_err("fieldbus_dev: unable to allocate char dev region\n" ); |
322 | goto err_alloc; |
323 | } |
324 | return 0; |
325 | |
326 | err_alloc: |
327 | class_unregister(class: &fieldbus_class); |
328 | return err; |
329 | } |
330 | |
331 | static void __exit fieldbus_exit(void) |
332 | { |
333 | unregister_chrdev_region(fieldbus_devt, MAX_FIELDBUSES); |
334 | class_unregister(class: &fieldbus_class); |
335 | ida_destroy(ida: &fieldbus_ida); |
336 | } |
337 | |
338 | subsys_initcall(fieldbus_init); |
339 | module_exit(fieldbus_exit); |
340 | |
341 | MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>" ); |
342 | MODULE_AUTHOR("Jonathan Stiles <jonathans@arcx.com>" ); |
343 | MODULE_DESCRIPTION("Fieldbus Device Driver Core" ); |
344 | MODULE_LICENSE("GPL v2" ); |
345 | |