346 lines · 7.7 KB · C
RawDownload
// 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 const struct class pps_gen_class = {	.name		= "pps-gen",	.dev_groups	= pps_gen_groups}; 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_unregister(&pps_gen_class);	unregister_chrdev_region(pps_gen_devt, PPS_GEN_MAX_SOURCES);} static int __init pps_gen_init(void){	int err; 	err = class_register(&pps_gen_class);	if (err) {		pr_err("failed to register class\n");		return err;	} 	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_unregister(&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");