| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * PPS generators core file
- *
- * Copyright (C) 2024 Rodolfo Giometti <giometti@enneenne.com>
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/sched.h>
- #include <linux/time.h>
- #include <linux/timex.h>
- #include <linux/uaccess.h>
- #include <linux/idr.h>
- #include <linux/cdev.h>
- #include <linux/poll.h>
- #include <linux/fs.h>
- #include <linux/pps_gen_kernel.h>
- #include <linux/slab.h>
- /*
- * Local variables
- */
- static dev_t pps_gen_devt;
- static struct class *pps_gen_class;
- static DEFINE_IDA(pps_gen_ida);
- /*
- * Char device methods
- */
- static __poll_t pps_gen_cdev_poll(struct file *file, poll_table *wait)
- {
- struct pps_gen_device *pps_gen = file->private_data;
- poll_wait(file, &pps_gen->queue, wait);
- return EPOLLIN | EPOLLRDNORM;
- }
- static int pps_gen_cdev_fasync(int fd, struct file *file, int on)
- {
- struct pps_gen_device *pps_gen = file->private_data;
- return fasync_helper(fd, file, on, &pps_gen->async_queue);
- }
- static long pps_gen_cdev_ioctl(struct file *file,
- unsigned int cmd, unsigned long arg)
- {
- struct pps_gen_device *pps_gen = file->private_data;
- void __user *uarg = (void __user *) arg;
- unsigned int __user *uiuarg = (unsigned int __user *) arg;
- unsigned int status;
- int ret;
- switch (cmd) {
- case PPS_GEN_SETENABLE:
- dev_dbg(pps_gen->dev, "PPS_GEN_SETENABLE\n");
- ret = get_user(status, uiuarg);
- if (ret)
- return -EFAULT;
- ret = pps_gen->info->enable(pps_gen, status);
- if (ret)
- return ret;
- pps_gen->enabled = status;
- break;
- case PPS_GEN_USESYSTEMCLOCK:
- dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n");
- ret = put_user(pps_gen->info->use_system_clock, uiuarg);
- if (ret)
- return -EFAULT;
- break;
- case PPS_GEN_FETCHEVENT: {
- struct pps_gen_event info;
- unsigned int ev = pps_gen->last_ev;
- dev_dbg(pps_gen->dev, "PPS_GEN_FETCHEVENT\n");
- ret = wait_event_interruptible(pps_gen->queue,
- ev != pps_gen->last_ev);
- if (ret == -ERESTARTSYS) {
- dev_dbg(pps_gen->dev, "pending signal caught\n");
- return -EINTR;
- }
- spin_lock_irq(&pps_gen->lock);
- info.sequence = pps_gen->sequence;
- info.event = pps_gen->event;
- spin_unlock_irq(&pps_gen->lock);
- ret = copy_to_user(uarg, &info, sizeof(struct pps_gen_event));
- if (ret)
- return -EFAULT;
- break;
- }
- default:
- return -ENOTTY;
- }
- return 0;
- }
- static int pps_gen_cdev_open(struct inode *inode, struct file *file)
- {
- struct pps_gen_device *pps_gen = container_of(inode->i_cdev,
- struct pps_gen_device, cdev);
- get_device(pps_gen->dev);
- file->private_data = pps_gen;
- return 0;
- }
- static int pps_gen_cdev_release(struct inode *inode, struct file *file)
- {
- struct pps_gen_device *pps_gen = file->private_data;
- put_device(pps_gen->dev);
- return 0;
- }
- /*
- * Char device stuff
- */
- static const struct file_operations pps_gen_cdev_fops = {
- .owner = THIS_MODULE,
- .poll = pps_gen_cdev_poll,
- .fasync = pps_gen_cdev_fasync,
- .unlocked_ioctl = pps_gen_cdev_ioctl,
- .open = pps_gen_cdev_open,
- .release = pps_gen_cdev_release,
- };
- static void pps_gen_device_destruct(struct device *dev)
- {
- struct pps_gen_device *pps_gen = dev_get_drvdata(dev);
- cdev_del(&pps_gen->cdev);
- pr_debug("deallocating pps-gen%d\n", pps_gen->id);
- ida_free(&pps_gen_ida, pps_gen->id);
- kfree(dev);
- kfree(pps_gen);
- }
- static int pps_gen_register_cdev(struct pps_gen_device *pps_gen)
- {
- int err;
- dev_t devt;
- err = ida_alloc_max(&pps_gen_ida, PPS_GEN_MAX_SOURCES - 1, GFP_KERNEL);
- if (err < 0) {
- if (err == -ENOSPC) {
- pr_err("too many PPS sources in the system\n");
- err = -EBUSY;
- }
- return err;
- }
- pps_gen->id = err;
- devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id);
- cdev_init(&pps_gen->cdev, &pps_gen_cdev_fops);
- pps_gen->cdev.owner = pps_gen->info->owner;
- err = cdev_add(&pps_gen->cdev, devt, 1);
- if (err) {
- pr_err("failed to add char device %d:%d\n",
- MAJOR(pps_gen_devt), pps_gen->id);
- goto free_ida;
- }
- pps_gen->dev = device_create(pps_gen_class, pps_gen->info->parent, devt,
- pps_gen, "pps-gen%d", pps_gen->id);
- if (IS_ERR(pps_gen->dev)) {
- err = PTR_ERR(pps_gen->dev);
- goto del_cdev;
- }
- pps_gen->dev->release = pps_gen_device_destruct;
- dev_set_drvdata(pps_gen->dev, pps_gen);
- pr_debug("generator got cdev (%d:%d)\n",
- MAJOR(pps_gen_devt), pps_gen->id);
- return 0;
- del_cdev:
- cdev_del(&pps_gen->cdev);
- free_ida:
- ida_free(&pps_gen_ida, pps_gen->id);
- return err;
- }
- static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen)
- {
- pr_debug("unregistering pps-gen%d\n", pps_gen->id);
- device_destroy(pps_gen_class, pps_gen->dev->devt);
- }
- /*
- * Exported functions
- */
- /**
- * pps_gen_register_source() - add a PPS generator in the system
- * @info: the PPS generator info struct
- *
- * This function is used to register a new PPS generator in the system.
- * When it returns successfully the new generator is up and running, and
- * it can be managed by the userspace.
- *
- * Return: the PPS generator device in case of success, and ERR_PTR(errno)
- * otherwise.
- */
- struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info *info)
- {
- struct pps_gen_device *pps_gen;
- int err;
- pps_gen = kzalloc_obj(struct pps_gen_device);
- if (pps_gen == NULL) {
- err = -ENOMEM;
- goto pps_gen_register_source_exit;
- }
- pps_gen->info = info;
- pps_gen->enabled = false;
- init_waitqueue_head(&pps_gen->queue);
- spin_lock_init(&pps_gen->lock);
- /* Create the char device */
- err = pps_gen_register_cdev(pps_gen);
- if (err < 0) {
- pr_err(" unable to create char device\n");
- goto kfree_pps_gen;
- }
- return pps_gen;
- kfree_pps_gen:
- kfree(pps_gen);
- pps_gen_register_source_exit:
- pr_err("unable to register generator\n");
- return ERR_PTR(err);
- }
- EXPORT_SYMBOL(pps_gen_register_source);
- /**
- * pps_gen_unregister_source() - remove a PPS generator from the system
- * @pps_gen: the PPS generator device to be removed
- *
- * This function is used to deregister a PPS generator from the system. When
- * called, it disables the generator so no pulses are generated anymore.
- */
- void pps_gen_unregister_source(struct pps_gen_device *pps_gen)
- {
- pps_gen_unregister_cdev(pps_gen);
- }
- EXPORT_SYMBOL(pps_gen_unregister_source);
- /* pps_gen_event - register a PPS generator event into the system
- * @pps: the PPS generator device
- * @event: the event type
- * @data: userdef pointer
- *
- * This function is used by each PPS generator in order to register a new
- * PPS event into the system (it's usually called inside an IRQ handler).
- */
- void pps_gen_event(struct pps_gen_device *pps_gen,
- unsigned int event, void *data)
- {
- unsigned long flags;
- dev_dbg(pps_gen->dev, "PPS generator event %u\n", event);
- spin_lock_irqsave(&pps_gen->lock, flags);
- pps_gen->event = event;
- pps_gen->sequence++;
- pps_gen->last_ev++;
- wake_up_interruptible_all(&pps_gen->queue);
- kill_fasync(&pps_gen->async_queue, SIGIO, POLL_IN);
- spin_unlock_irqrestore(&pps_gen->lock, flags);
- }
- EXPORT_SYMBOL(pps_gen_event);
- /*
- * Module stuff
- */
- static void __exit pps_gen_exit(void)
- {
- class_destroy(pps_gen_class);
- unregister_chrdev_region(pps_gen_devt, PPS_GEN_MAX_SOURCES);
- }
- static int __init pps_gen_init(void)
- {
- int err;
- pps_gen_class = class_create("pps-gen");
- if (IS_ERR(pps_gen_class)) {
- pr_err("failed to allocate class\n");
- return PTR_ERR(pps_gen_class);
- }
- pps_gen_class->dev_groups = pps_gen_groups;
- err = alloc_chrdev_region(&pps_gen_devt, 0,
- PPS_GEN_MAX_SOURCES, "pps-gen");
- if (err < 0) {
- pr_err("failed to allocate char device region\n");
- goto remove_class;
- }
- return 0;
- remove_class:
- class_destroy(pps_gen_class);
- return err;
- }
- subsys_initcall(pps_gen_init);
- module_exit(pps_gen_exit);
- MODULE_AUTHOR("Rodolfo Giometti <giometti@enneenne.com>");
- MODULE_DESCRIPTION("LinuxPPS generators support");
- MODULE_LICENSE("GPL");
|