projects/linux·blob master · refs

346 lines · 7.7 KiB

1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * PPS generators core file
4 *
5 * Copyright (C) 2024 Rodolfo Giometti <[email protected]>
6 */
7
8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9
10#include <linux/kernel.h>
11#include <linux/module.h>
12#include <linux/init.h>
13#include <linux/sched.h>
14#include <linux/time.h>
15#include <linux/timex.h>
16#include <linux/uaccess.h>
17#include <linux/idr.h>
18#include <linux/cdev.h>
19#include <linux/poll.h>
20#include <linux/fs.h>
21#include <linux/pps_gen_kernel.h>
22#include <linux/slab.h>
23
24/*
25 * Local variables
26 */
27
28static dev_t pps_gen_devt;
29static const struct class pps_gen_class = {
30 .name = "pps-gen",
31 .dev_groups = pps_gen_groups
32};
33
34static DEFINE_IDA(pps_gen_ida);
35
36/*
37 * Char device methods
38 */
39
40static __poll_t pps_gen_cdev_poll(struct file *file, poll_table *wait)
41{
42 struct pps_gen_device *pps_gen = file->private_data;
43
44 poll_wait(file, &pps_gen->queue, wait);
45 return EPOLLIN | EPOLLRDNORM;
46}
47
48static int pps_gen_cdev_fasync(int fd, struct file *file, int on)
49{
50 struct pps_gen_device *pps_gen = file->private_data;
51
52 return fasync_helper(fd, file, on, &pps_gen->async_queue);
53}
54
55static long pps_gen_cdev_ioctl(struct file *file,
56 unsigned int cmd, unsigned long arg)
57{
58 struct pps_gen_device *pps_gen = file->private_data;
59 void __user *uarg = (void __user *) arg;
60 unsigned int __user *uiuarg = (unsigned int __user *) arg;
61 unsigned int status;
62 int ret;
63
64 switch (cmd) {
65 case PPS_GEN_SETENABLE:
66 dev_dbg(pps_gen->dev, "PPS_GEN_SETENABLE\n");
67
68 ret = get_user(status, uiuarg);
69 if (ret)
70 return -EFAULT;
71
72 ret = pps_gen->info->enable(pps_gen, status);
73 if (ret)
74 return ret;
75 pps_gen->enabled = status;
76
77 break;
78
79 case PPS_GEN_USESYSTEMCLOCK:
80 dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n");
81
82 ret = put_user(pps_gen->info->use_system_clock, uiuarg);
83 if (ret)
84 return -EFAULT;
85
86 break;
87
88 case PPS_GEN_FETCHEVENT: {
89 struct pps_gen_event info;
90 unsigned int ev = pps_gen->last_ev;
91
92 dev_dbg(pps_gen->dev, "PPS_GEN_FETCHEVENT\n");
93
94 ret = wait_event_interruptible(pps_gen->queue,
95 ev != pps_gen->last_ev);
96 if (ret == -ERESTARTSYS) {
97 dev_dbg(pps_gen->dev, "pending signal caught\n");
98 return -EINTR;
99 }
100
101 spin_lock_irq(&pps_gen->lock);
102 info.sequence = pps_gen->sequence;
103 info.event = pps_gen->event;
104 spin_unlock_irq(&pps_gen->lock);
105
106 ret = copy_to_user(uarg, &info, sizeof(struct pps_gen_event));
107 if (ret)
108 return -EFAULT;
109
110 break;
111 }
112 default:
113 return -ENOTTY;
114 }
115
116 return 0;
117}
118
119static int pps_gen_cdev_open(struct inode *inode, struct file *file)
120{
121 struct pps_gen_device *pps_gen = container_of(inode->i_cdev,
122 struct pps_gen_device, cdev);
123
124 get_device(pps_gen->dev);
125 file->private_data = pps_gen;
126 return 0;
127}
128
129static int pps_gen_cdev_release(struct inode *inode, struct file *file)
130{
131 struct pps_gen_device *pps_gen = file->private_data;
132
133 put_device(pps_gen->dev);
134 return 0;
135}
136
137/*
138 * Char device stuff
139 */
140
141static const struct file_operations pps_gen_cdev_fops = {
142 .owner = THIS_MODULE,
143 .poll = pps_gen_cdev_poll,
144 .fasync = pps_gen_cdev_fasync,
145 .unlocked_ioctl = pps_gen_cdev_ioctl,
146 .open = pps_gen_cdev_open,
147 .release = pps_gen_cdev_release,
148};
149
150static void pps_gen_device_destruct(struct device *dev)
151{
152 struct pps_gen_device *pps_gen = dev_get_drvdata(dev);
153
154 cdev_del(&pps_gen->cdev);
155
156 pr_debug("deallocating pps-gen%d\n", pps_gen->id);
157 ida_free(&pps_gen_ida, pps_gen->id);
158
159 kfree(dev);
160 kfree(pps_gen);
161}
162
163static int pps_gen_register_cdev(struct pps_gen_device *pps_gen)
164{
165 int err;
166 dev_t devt;
167
168 err = ida_alloc_max(&pps_gen_ida, PPS_GEN_MAX_SOURCES - 1, GFP_KERNEL);
169 if (err < 0) {
170 if (err == -ENOSPC) {
171 pr_err("too many PPS sources in the system\n");
172 err = -EBUSY;
173 }
174 return err;
175 }
176 pps_gen->id = err;
177
178 devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id);
179
180 cdev_init(&pps_gen->cdev, &pps_gen_cdev_fops);
181 pps_gen->cdev.owner = pps_gen->info->owner;
182
183 err = cdev_add(&pps_gen->cdev, devt, 1);
184 if (err) {
185 pr_err("failed to add char device %d:%d\n",
186 MAJOR(pps_gen_devt), pps_gen->id);
187 goto free_ida;
188 }
189 pps_gen->dev = device_create(&pps_gen_class, pps_gen->info->parent, devt,
190 pps_gen, "pps-gen%d", pps_gen->id);
191 if (IS_ERR(pps_gen->dev)) {
192 err = PTR_ERR(pps_gen->dev);
193 goto del_cdev;
194 }
195 pps_gen->dev->release = pps_gen_device_destruct;
196 dev_set_drvdata(pps_gen->dev, pps_gen);
197
198 pr_debug("generator got cdev (%d:%d)\n",
199 MAJOR(pps_gen_devt), pps_gen->id);
200
201 return 0;
202
203del_cdev:
204 cdev_del(&pps_gen->cdev);
205free_ida:
206 ida_free(&pps_gen_ida, pps_gen->id);
207 return err;
208}
209
210static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen)
211{
212 pr_debug("unregistering pps-gen%d\n", pps_gen->id);
213 device_destroy(&pps_gen_class, pps_gen->dev->devt);
214}
215
216/*
217 * Exported functions
218 */
219
220/**
221 * pps_gen_register_source() - add a PPS generator in the system
222 * @info: the PPS generator info struct
223 *
224 * This function is used to register a new PPS generator in the system.
225 * When it returns successfully the new generator is up and running, and
226 * it can be managed by the userspace.
227 *
228 * Return: the PPS generator device in case of success, and ERR_PTR(errno)
229 * otherwise.
230 */
231struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info *info)
232{
233 struct pps_gen_device *pps_gen;
234 int err;
235
236 pps_gen = kzalloc_obj(struct pps_gen_device);
237 if (pps_gen == NULL) {
238 err = -ENOMEM;
239 goto pps_gen_register_source_exit;
240 }
241 pps_gen->info = info;
242 pps_gen->enabled = false;
243
244 init_waitqueue_head(&pps_gen->queue);
245 spin_lock_init(&pps_gen->lock);
246
247 /* Create the char device */
248 err = pps_gen_register_cdev(pps_gen);
249 if (err < 0) {
250 pr_err(" unable to create char device\n");
251 goto kfree_pps_gen;
252 }
253
254 return pps_gen;
255
256kfree_pps_gen:
257 kfree(pps_gen);
258
259pps_gen_register_source_exit:
260 pr_err("unable to register generator\n");
261
262 return ERR_PTR(err);
263}
264EXPORT_SYMBOL(pps_gen_register_source);
265
266/**
267 * pps_gen_unregister_source() - remove a PPS generator from the system
268 * @pps_gen: the PPS generator device to be removed
269 *
270 * This function is used to deregister a PPS generator from the system. When
271 * called, it disables the generator so no pulses are generated anymore.
272 */
273void pps_gen_unregister_source(struct pps_gen_device *pps_gen)
274{
275 pps_gen_unregister_cdev(pps_gen);
276}
277EXPORT_SYMBOL(pps_gen_unregister_source);
278
279/* pps_gen_event - register a PPS generator event into the system
280 * @pps: the PPS generator device
281 * @event: the event type
282 * @data: userdef pointer
283 *
284 * This function is used by each PPS generator in order to register a new
285 * PPS event into the system (it's usually called inside an IRQ handler).
286 */
287void pps_gen_event(struct pps_gen_device *pps_gen,
288 unsigned int event, void *data)
289{
290 unsigned long flags;
291
292 dev_dbg(pps_gen->dev, "PPS generator event %u\n", event);
293
294 spin_lock_irqsave(&pps_gen->lock, flags);
295
296 pps_gen->event = event;
297 pps_gen->sequence++;
298
299 pps_gen->last_ev++;
300 wake_up_interruptible_all(&pps_gen->queue);
301 kill_fasync(&pps_gen->async_queue, SIGIO, POLL_IN);
302
303 spin_unlock_irqrestore(&pps_gen->lock, flags);
304}
305EXPORT_SYMBOL(pps_gen_event);
306
307/*
308 * Module stuff
309 */
310
311static void __exit pps_gen_exit(void)
312{
313 class_unregister(&pps_gen_class);
314 unregister_chrdev_region(pps_gen_devt, PPS_GEN_MAX_SOURCES);
315}
316
317static int __init pps_gen_init(void)
318{
319 int err;
320
321 err = class_register(&pps_gen_class);
322 if (err) {
323 pr_err("failed to register class\n");
324 return err;
325 }
326
327 err = alloc_chrdev_region(&pps_gen_devt, 0,
328 PPS_GEN_MAX_SOURCES, "pps-gen");
329 if (err < 0) {
330 pr_err("failed to allocate char device region\n");
331 goto remove_class;
332 }
333
334 return 0;
335
336remove_class:
337 class_unregister(&pps_gen_class);
338 return err;
339}
340
341subsys_initcall(pps_gen_init);
342module_exit(pps_gen_exit);
343
344MODULE_AUTHOR("Rodolfo Giometti <[email protected]>");
345MODULE_DESCRIPTION("LinuxPPS generators support");
346MODULE_LICENSE("GPL");