// 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");
// 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");