File Index
Files and directories
Sign in
FILE

pps_gen.c

File preview with syntax highlighting.

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