From 22098763a10d9c1340827fcf6edab66f153b27f0 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 13 Sep 2026 12:27:00 -0700 Subject: Merge tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: - Don't destroy user event fields when removal fails User event fields are destroyed before the event is removed from visibility. But that can fail leaving the still visible event with no fields. Move the destroying of the fields to after the event is successfully removed from visibility. - Initialize function graph state is fork before calling copy_exec_state() For non-CLONE_VM forks, copy_exec_state() allocates a new task_exec_state. If that allocation fails, ftrace_graph_exit_task() will free the tasks ret_stack pointer. Since that pointer is still using the parent's ret_stack, it mistakenly frees the parent's pointer too. Call ftrace_graph_init() on the task first which will NULL out the new tasks's ret_stack and if the copy fails, it will not free anything. - Remove FGRAPH_MAX_INDEX The macro FGRAPH_MAX_INDEX was added but never used. Remove it. - Save ent_size in function graph printing of nested functions The function graph tracer needs to look at the next event to see if the next event is the return of the current function entry. If it is, it prints a single line: ktime_get(); Otherwise it prints it like a nested function: tick_nohz_irq_exit() { ktime_get(); kcpustat_irq_exit(); } In order to look at the next event, it must save the current event so that it has the information to print from it. It saves the event in the iterator descriptor called "ent". What it doesn't save is the ent_size of the event which is now used to know if the function graph arguments are to be printed. The peek doesn't save the size so the size used happens to be that of the size of the last event that was seen. Save the entry event size in the iterator descriptor so that the correct size is used. - Fix several errors with freeing data in the histogram code The histogram code had a lot of leaked or or incorrect accounting when failures happen. Correct them. - Fix histogram regression of .percent and .graph modifiers Up until 6.3 histogram values could have "percent" or "graph" modifiers that changed how they were printed. But a change that added restricting histograms values from being strings, stack traces and other modifiers inadvertently prevented them from using the percent and graph modifiers, which were legal use cases for values. Put back the percent and graph modifiers. - Fix various typos in the comments - Set the trace_clock before initializing a histogram with clock argument The histogram API allows the user to specific which trace clock to use via a "clock=" string. The histogram is set up first before the clock is checked. If the passed in clock is not valid, it exits without fully fixing up the histogram leaving it on the list and a use-after-free can trigger. Update the clock argument first and if it fails then exit gracefully before the histogram trigger is placed on any lists. - Restore :mod: trailer after parsing in ftrace_set_clr_event The function ftrace_set_clr_event() modifies the parse string and needs to put it back to what was passed in. It searches for ":mod:" via a strsep() but fails to put back the first ':' in the string. Add back the ':' in the passed in string. - Take trace_array reference when opening a tracer options file The options files are dynamically created and some tracers add their own options. When a tracer adds their own list of options, the trace_array holding them has an array to hold the list of options for each tracer. This array increases in size via a krealloc(), and the new entry gets a newly allocated array to hold the options of the new tracer being added. The element in each entry of the tracer's option array holds a pointer back to the trace_array, a pointer to the tracer it is associated to, a pointer to the flags of the option. The issue is that these arrays are freed when the trace_array is freed when its instance it represents is removed from the instances directory. There's a race that an open of one of these options files can happen when the instance is being removed. Add a new helper function to be called by the open function of the options file to iterate all existing trace_arrays under a lock and find the one that has the given option element in one of it's tracer arrays. If found, then update the associated trace_array's reference counter to keep it from being freed. If not found, have the open call return -ENODEV. - Disable interrupts when acquiring the lock in rb_wake_up_waiters() The function rb_wake_up_waiters() assumes it will be called in interrupt context and does not disable irqs when taking cpu_buffer->reader_lock, which can be called in hard interrupt context. The issue is in PREEMPT_RT, this function is called in thread context leaving this lock open to a deadlock. Take the lock with interrupts disabled. - Use rcu_assign_pointer() for tmp_ops filter hash The tmp_ops used in update_ftrace_direct_mod() assigns its filter_hash field directly, but that field is annotated as __rcu and sparse complains. Assign it with rcu_assign_pointer() - Fix use-after-free in enable_trigger_private_data_free() The trace_event_call is accessed through the event_trigger_data's trace_event_file pointer to put the trace_event_call on freeing. The issue is that the trace_event_file data may have been freed already causing a use-after-free. Add a field to the event_trigger_data that points directly to the trace_event_call so that it can decrement its reference directly without needing to go through the trace_event_file. - Fix accounting of buffer data remote headers trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount the number of pages is needed for the asked for size as it doesn't take into account the meta data on each page. Add a helper function to do the calculation properly and use that in these functions. - Catch nr_page_va overflow in ring_buffer_desc sizing The number of pages per remote ring buffer is capped by ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to overflow that field would silently allocate a descriptor smaller than what was asked for. - Do not resize the subbuf order if any per_cpu buffer is disabled The mmapping of ring buffers disables resizing the subbuffers, but it is done per-cpu whereas the subbuf size change is done for all the per_cpu buffers under the buffer->mutex. It could change the size of some while the mapping is happening on others. Have the resize of the subbuf order check all the per_cpu buffers under the lock to see if any of them is disabled before starting and causing an inconsistency between buffers that are being mapped. * tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (25 commits) ring-buffer: Check resize_disabled before publishing the new subbuf order tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing tracing/remotes: Account for ring buffer page header in size calculation tracing: Don't dereference trace_event_file in deferred trigger free ftrace: Use rcu_assign_pointer() for tmp_ops filter hash ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters() tracing: Take trace_array reference when opening a tracer options file tracing: Fix ring_buffer_read_page_size() kernel-doc tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event() tracing: Fix memory corruption from a "STACKTRACE" histogram key tracing: Fix memory corruption from the histogram stacktrace modifier tracing: Undo the registration when enabling the histogram trigger fails tracing: Take the reference before publishing the named histogram trigger tracing: Set the trace clock before registering the histogram trigger tracing: Fix typo "preceeded" in comment tracing: Fix typo "availabe" in comment tracing: Let histogram values keep the percent and graph modifiers tracing: Keep the entry count when the histogram stats allocation fails tracing: Free histogram the field rejected for a bad modifier tracing: Free histogram the var ref when its initialization fails ... --- drivers/pps/Kconfig | 45 +++ drivers/pps/Makefile | 12 + drivers/pps/clients/Kconfig | 37 +++ drivers/pps/clients/Makefile | 11 + drivers/pps/clients/pps-gpio.c | 242 ++++++++++++++++ drivers/pps/clients/pps-ktimer.c | 87 ++++++ drivers/pps/clients/pps-ldisc.c | 141 +++++++++ drivers/pps/clients/pps_parport.c | 228 +++++++++++++++ drivers/pps/generators/Kconfig | 42 +++ drivers/pps/generators/Makefile | 12 + drivers/pps/generators/pps_gen-dummy.c | 92 ++++++ drivers/pps/generators/pps_gen.c | 346 ++++++++++++++++++++++ drivers/pps/generators/pps_gen_tio.c | 267 +++++++++++++++++ drivers/pps/generators/sysfs.c | 75 +++++ drivers/pps/kapi.c | 219 ++++++++++++++ drivers/pps/kc.c | 119 ++++++++ drivers/pps/kc.h | 33 +++ drivers/pps/pps.c | 509 +++++++++++++++++++++++++++++++++ drivers/pps/sysfs.c | 99 +++++++ 19 files changed, 2616 insertions(+) create mode 100644 drivers/pps/Kconfig create mode 100644 drivers/pps/Makefile create mode 100644 drivers/pps/clients/Kconfig create mode 100644 drivers/pps/clients/Makefile create mode 100644 drivers/pps/clients/pps-gpio.c create mode 100644 drivers/pps/clients/pps-ktimer.c create mode 100644 drivers/pps/clients/pps-ldisc.c create mode 100644 drivers/pps/clients/pps_parport.c create mode 100644 drivers/pps/generators/Kconfig create mode 100644 drivers/pps/generators/Makefile create mode 100644 drivers/pps/generators/pps_gen-dummy.c create mode 100644 drivers/pps/generators/pps_gen.c create mode 100644 drivers/pps/generators/pps_gen_tio.c create mode 100644 drivers/pps/generators/sysfs.c create mode 100644 drivers/pps/kapi.c create mode 100644 drivers/pps/kc.c create mode 100644 drivers/pps/kc.h create mode 100644 drivers/pps/pps.c create mode 100644 drivers/pps/sysfs.c (limited to 'drivers/pps') diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig new file mode 100644 index 000000000..e1651d51c --- /dev/null +++ b/drivers/pps/Kconfig @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PPS support configuration +# + +menuconfig PPS + tristate "PPS support" + help + PPS (Pulse Per Second) is a special pulse provided by some GPS + antennae. Userland can use it to get a high-precision time + reference. + + Some antennae's PPS signals are connected with the CD (Carrier + Detect) pin of the serial line they use to communicate with the + host. In this case use the SERIAL_LINE client support. + + Some antennae's PPS signals are connected with some special host + inputs so you have to enable the corresponding client support. + + To compile this driver as a module, choose M here: the module + will be called pps_core.ko. + +if PPS + +config PPS_DEBUG + bool "PPS debugging messages" + help + Say Y here if you want the PPS support to produce a bunch of debug + messages to the system log. Select this if you are having a + problem with PPS support and want to see more of what is going on. + +config NTP_PPS + bool "PPS kernel consumer support" + depends on !NO_HZ_COMMON + help + This option adds support for direct in-kernel time + synchronization using an external PPS signal. + + It doesn't work on tickless systems at the moment. + +source "drivers/pps/clients/Kconfig" + +source "drivers/pps/generators/Kconfig" + +endif # PPS diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile new file mode 100644 index 000000000..0aea394d4 --- /dev/null +++ b/drivers/pps/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the PPS core. +# + +pps_core-y := pps.o kapi.o sysfs.o +pps_core-$(CONFIG_NTP_PPS) += kc.o +obj-$(CONFIG_PPS) := pps_core.o +obj-y += clients/ +obj-$(CONFIG_PPS_GENERATOR) += generators/ + +ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/clients/Kconfig b/drivers/pps/clients/Kconfig new file mode 100644 index 000000000..4f3244e17 --- /dev/null +++ b/drivers/pps/clients/Kconfig @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PPS clients configuration +# + +comment "PPS clients support" + +config PPS_CLIENT_KTIMER + tristate "Kernel timer client (Testing client, use for debug)" + help + If you say yes here you get support for a PPS debugging client + which uses a kernel timer to generate the PPS signal. + + This driver can also be built as a module. If so, the module + will be called pps-ktimer. + +config PPS_CLIENT_LDISC + tristate "PPS line discipline" + depends on TTY + help + If you say yes here you get support for a PPS source connected + with the CD (Carrier Detect) pin of your serial port. + +config PPS_CLIENT_PARPORT + tristate "Parallel port PPS client" + depends on PARPORT + help + If you say yes here you get support for a PPS source connected + with the interrupt pin of your parallel port. + +config PPS_CLIENT_GPIO + tristate "PPS client using GPIO" + help + If you say yes here you get support for a PPS source using + GPIO. To be useful you must also register a platform device + specifying the GPIO pin and other options, usually in your board + setup. diff --git a/drivers/pps/clients/Makefile b/drivers/pps/clients/Makefile new file mode 100644 index 000000000..7a3807e57 --- /dev/null +++ b/drivers/pps/clients/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for PPS clients. +# + +obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o +obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o +obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o +obj-$(CONFIG_PPS_CLIENT_GPIO) += pps-gpio.o + +ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c new file mode 100644 index 000000000..73ec2c733 --- /dev/null +++ b/drivers/pps/clients/pps-gpio.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pps-gpio.c -- PPS client driver using GPIO + * + * Copyright (C) 2010 Ricardo Martins + * Copyright (C) 2011 James Nuss + */ + +#define PPS_GPIO_NAME "pps-gpio" +#define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Info for each registered platform device */ +struct pps_gpio_device_data { + int irq; /* IRQ used as PPS source */ + struct pps_device *pps; /* PPS source device */ + struct pps_source_info info; /* PPS source information */ + struct gpio_desc *gpio_pin; /* GPIO port descriptors */ + struct gpio_desc *echo_pin; + struct timer_list echo_timer; /* timer to reset echo active state */ + bool assert_falling_edge; + unsigned int echo_active_ms; /* PPS echo active duration */ + unsigned long echo_timeout; /* timer timeout value in jiffies */ + struct pps_event_time ts; /* timestamp captured in hardirq */ +}; + +/* + * Report the PPS event + */ + +/* + * Primary hardirq handler -- runs in hardirq context even on PREEMPT_RT. + * Only captures the timestamp; all other work is deferred to the thread. + */ +static irqreturn_t pps_gpio_irq_hardirq(int irq, void *data) +{ + struct pps_gpio_device_data *info = data; + + pps_get_ts(&info->ts); + + return IRQ_WAKE_THREAD; +} + +/* + * Threaded handler -- processes the PPS event using the timestamp + * captured in hardirq context above. + */ +static irqreturn_t pps_gpio_irq_thread(int irq, void *data) +{ + struct pps_gpio_device_data *info = data; + + pps_event(info->pps, &info->ts, PPS_CAPTUREASSERT, data); + + return IRQ_HANDLED; +} + +/* This function will only be called when an ECHO GPIO is defined */ +static void pps_gpio_echo(struct pps_device *pps, int event, void *data) +{ + /* add_timer() needs to write into info->echo_timer */ + struct pps_gpio_device_data *info = data; + + switch (event) { + case PPS_CAPTUREASSERT: + if (pps->params.mode & PPS_ECHOASSERT) + gpiod_set_value(info->echo_pin, 1); + break; + } + + /* fire the timer */ + if (info->pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) { + info->echo_timer.expires = jiffies + info->echo_timeout; + add_timer(&info->echo_timer); + } +} + +/* Timer callback to reset the echo pin to the inactive state */ +static void pps_gpio_echo_timer_callback(struct timer_list *t) +{ + const struct pps_gpio_device_data *info; + + info = timer_container_of(info, t, echo_timer); + + gpiod_set_value(info->echo_pin, 0); +} + +static int pps_gpio_setup(struct device *dev) +{ + struct pps_gpio_device_data *data = dev_get_drvdata(dev); + int ret; + u32 value; + + data->gpio_pin = devm_gpiod_get(dev, NULL, GPIOD_IN); + if (IS_ERR(data->gpio_pin)) + return dev_err_probe(dev, PTR_ERR(data->gpio_pin), + "failed to request PPS GPIO\n"); + + data->assert_falling_edge = + device_property_read_bool(dev, "assert-falling-edge"); + + data->echo_pin = devm_gpiod_get_optional(dev, "echo", GPIOD_OUT_LOW); + if (IS_ERR(data->echo_pin)) + return dev_err_probe(dev, PTR_ERR(data->echo_pin), + "failed to request ECHO GPIO\n"); + + if (!data->echo_pin) + return 0; + + ret = device_property_read_u32(dev, "echo-active-ms", &value); + if (ret) { + dev_err(dev, "failed to get echo-active-ms from FW\n"); + return ret; + } + + /* sanity check on echo_active_ms */ + if (!value || value > 999) { + dev_err(dev, "echo-active-ms: %u - bad value from FW\n", value); + return -EINVAL; + } + + data->echo_active_ms = value; + + return 0; +} + +static unsigned long +get_irqf_trigger_flags(const struct pps_gpio_device_data *data) +{ + return data->assert_falling_edge ? IRQF_TRIGGER_FALLING : + IRQF_TRIGGER_RISING; +} + +static int pps_gpio_probe(struct platform_device *pdev) +{ + struct pps_gpio_device_data *data; + struct device *dev = &pdev->dev; + int ret; + int pps_default_params; + + /* allocate space for device info */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_set_drvdata(dev, data); + + /* GPIO setup */ + ret = pps_gpio_setup(dev); + if (ret) + return ret; + + /* IRQ setup */ + ret = gpiod_to_irq(data->gpio_pin); + if (ret < 0) { + dev_err(dev, "failed to map GPIO to IRQ: %d\n", ret); + return -EINVAL; + } + data->irq = ret; + + /* initialize PPS specific parts of the bookkeeping data structure. */ + data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | + PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC; + data->info.owner = THIS_MODULE; + snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d", + pdev->name, pdev->id); + if (data->echo_pin) { + data->info.echo = pps_gpio_echo; + data->echo_timeout = msecs_to_jiffies(data->echo_active_ms); + timer_setup(&data->echo_timer, pps_gpio_echo_timer_callback, 0); + } + + /* register PPS source */ + pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT; + data->pps = pps_register_source(&data->info, pps_default_params); + if (IS_ERR(data->pps)) { + dev_err(dev, "failed to register IRQ %d as PPS source\n", + data->irq); + return PTR_ERR(data->pps); + } + + /* register IRQ interrupt handler */ + ret = request_threaded_irq(data->irq, + pps_gpio_irq_hardirq, pps_gpio_irq_thread, + get_irqf_trigger_flags(data) | IRQF_ONESHOT, + data->info.name, data); + if (ret) { + pps_unregister_source(data->pps); + dev_err(dev, "failed to acquire IRQ %d\n", data->irq); + return -EINVAL; + } + + dev_dbg(&data->pps->dev, "Registered IRQ %d as PPS source\n", + data->irq); + + return 0; +} + +static void pps_gpio_remove(struct platform_device *pdev) +{ + struct pps_gpio_device_data *data = platform_get_drvdata(pdev); + + free_irq(data->irq, data); + pps_unregister_source(data->pps); + timer_delete_sync(&data->echo_timer); + /* reset echo pin in any case */ + gpiod_set_value(data->echo_pin, 0); + dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq); +} + +static const struct of_device_id pps_gpio_dt_ids[] = { + { .compatible = "pps-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pps_gpio_dt_ids); + +static struct platform_driver pps_gpio_driver = { + .probe = pps_gpio_probe, + .remove = pps_gpio_remove, + .driver = { + .name = PPS_GPIO_NAME, + .of_match_table = pps_gpio_dt_ids, + }, +}; + +module_platform_driver(pps_gpio_driver); +MODULE_AUTHOR("Ricardo Martins "); +MODULE_AUTHOR("James Nuss "); +MODULE_DESCRIPTION("Use GPIO pin as PPS source"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.2.0"); diff --git a/drivers/pps/clients/pps-ktimer.c b/drivers/pps/clients/pps-ktimer.c new file mode 100644 index 000000000..121bd29d8 --- /dev/null +++ b/drivers/pps/clients/pps-ktimer.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pps-ktimer.c -- kernel timer test client + * + * Copyright (C) 2005-2006 Rodolfo Giometti + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +/* + * Global variables + */ + +static struct pps_device *pps; +static struct timer_list ktimer; + +/* + * The kernel timer + */ + +static void pps_ktimer_event(struct timer_list *unused) +{ + struct pps_event_time ts; + + /* First of all we get the time stamp... */ + pps_get_ts(&ts); + + pps_event(pps, &ts, PPS_CAPTUREASSERT, NULL); + + mod_timer(&ktimer, jiffies + HZ); +} + +/* + * The PPS info struct + */ + +static struct pps_source_info pps_ktimer_info = { + .name = "ktimer", + .path = "", + .mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | + PPS_ECHOASSERT | + PPS_CANWAIT | PPS_TSFMT_TSPEC, + .owner = THIS_MODULE, +}; + +/* + * Module staff + */ + +static void __exit pps_ktimer_exit(void) +{ + dev_dbg(&pps->dev, "ktimer PPS source unregistered\n"); + + timer_delete_sync(&ktimer); + pps_unregister_source(pps); +} + +static int __init pps_ktimer_init(void) +{ + pps = pps_register_source(&pps_ktimer_info, + PPS_CAPTUREASSERT | PPS_OFFSETASSERT); + if (IS_ERR(pps)) { + pr_err("cannot register PPS source\n"); + return PTR_ERR(pps); + } + + timer_setup(&ktimer, pps_ktimer_event, 0); + mod_timer(&ktimer, jiffies + HZ); + + dev_dbg(&pps->dev, "ktimer PPS source registered\n"); + + return 0; +} + +module_init(pps_ktimer_init); +module_exit(pps_ktimer_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("dummy PPS source by using a kernel timer (just for debug)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/clients/pps-ldisc.c b/drivers/pps/clients/pps-ldisc.c new file mode 100644 index 000000000..fa5660f3c --- /dev/null +++ b/drivers/pps/clients/pps-ldisc.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pps-ldisc.c -- PPS line discipline + * + * Copyright (C) 2008 Rodolfo Giometti + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +static void pps_tty_dcd_change(struct tty_struct *tty, bool active) +{ + struct pps_device *pps; + struct pps_event_time ts; + + pps_get_ts(&ts); + + pps = pps_lookup_dev(tty); + /* + * This should never fail, but the ldisc locking is very + * convoluted, so don't crash just in case. + */ + if (WARN_ON_ONCE(pps == NULL)) + return; + + /* Now do the PPS event report */ + pps_event(pps, &ts, active ? PPS_CAPTUREASSERT : + PPS_CAPTURECLEAR, NULL); + + dev_dbg(&pps->dev, "PPS %s at %lu\n", + active ? "assert" : "clear", jiffies); +} + +static int (*alias_n_tty_open)(struct tty_struct *tty); + +static int pps_tty_open(struct tty_struct *tty) +{ + struct pps_source_info info; + struct tty_driver *drv = tty->driver; + int index = tty->index + drv->name_base; + struct pps_device *pps; + int ret; + + info.owner = THIS_MODULE; + info.dev = NULL; + snprintf(info.name, PPS_MAX_NAME_LEN, "%s%d", drv->driver_name, index); + snprintf(info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", drv->name, index); + info.mode = PPS_CAPTUREBOTH | \ + PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ + PPS_CANWAIT | PPS_TSFMT_TSPEC; + + pps = pps_register_source(&info, PPS_CAPTUREBOTH | \ + PPS_OFFSETASSERT | PPS_OFFSETCLEAR); + if (IS_ERR(pps)) { + pr_err("cannot register PPS source \"%s\"\n", info.path); + return PTR_ERR(pps); + } + pps->lookup_cookie = tty; + + /* Now open the base class N_TTY ldisc */ + ret = alias_n_tty_open(tty); + if (ret < 0) { + pr_err("cannot open tty ldisc \"%s\"\n", info.path); + goto err_unregister; + } + + dev_dbg(&pps->dev, "source \"%s\" added\n", info.path); + + return 0; + +err_unregister: + pps_unregister_source(pps); + return ret; +} + +static void (*alias_n_tty_close)(struct tty_struct *tty); + +static void pps_tty_close(struct tty_struct *tty) +{ + struct pps_device *pps = pps_lookup_dev(tty); + + alias_n_tty_close(tty); + + if (WARN_ON(!pps)) + return; + + dev_info(&pps->dev, "removed\n"); + pps_unregister_source(pps); +} + +static struct tty_ldisc_ops pps_ldisc_ops; + +/* + * Module stuff + */ + +static int __init pps_tty_init(void) +{ + int err; + + /* Inherit the N_TTY's ops */ + n_tty_inherit_ops(&pps_ldisc_ops); + + /* Save N_TTY's open()/close() methods */ + alias_n_tty_open = pps_ldisc_ops.open; + alias_n_tty_close = pps_ldisc_ops.close; + + /* Init PPS_TTY data */ + pps_ldisc_ops.owner = THIS_MODULE; + pps_ldisc_ops.num = N_PPS; + pps_ldisc_ops.name = "pps_tty"; + pps_ldisc_ops.dcd_change = pps_tty_dcd_change; + pps_ldisc_ops.open = pps_tty_open; + pps_ldisc_ops.close = pps_tty_close; + + err = tty_register_ldisc(&pps_ldisc_ops); + if (err) + pr_err("can't register PPS line discipline\n"); + else + pr_info("PPS line discipline registered\n"); + + return err; +} + +static void __exit pps_tty_cleanup(void) +{ + tty_unregister_ldisc(&pps_ldisc_ops); +} + +module_init(pps_tty_init); +module_exit(pps_tty_cleanup); + +MODULE_ALIAS_LDISC(N_PPS); +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("PPS TTY device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/clients/pps_parport.c b/drivers/pps/clients/pps_parport.c new file mode 100644 index 000000000..f5af18bc2 --- /dev/null +++ b/drivers/pps/clients/pps_parport.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pps_parport.c -- kernel parallel port PPS client + * + * Copyright (C) 2009 Alexander Gordeev + */ + + +/* + * TODO: + * implement echo over SEL pin + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +/* module parameters */ + +#define CLEAR_WAIT_MAX 100 +#define CLEAR_WAIT_MAX_ERRORS 5 + +static unsigned int clear_wait = 100; +MODULE_PARM_DESC(clear_wait, + "Maximum number of port reads when polling for signal clear," + " zero turns clear edge capture off entirely"); +module_param(clear_wait, uint, 0); + +static DEFINE_IDA(pps_client_index); + +/* internal per port structure */ +struct pps_client_pp { + struct pardevice *pardev; /* parport device */ + struct pps_device *pps; /* PPS device */ + unsigned int cw; /* port clear timeout */ + unsigned int cw_err; /* number of timeouts */ + int index; /* device number */ +}; + +static inline int signal_is_set(struct parport *port) +{ + return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0; +} + +/* parport interrupt handler */ +static void parport_irq(void *handle) +{ + struct pps_event_time ts_assert, ts_clear; + struct pps_client_pp *dev = handle; + struct parport *port = dev->pardev->port; + unsigned int i; + unsigned long flags; + + /* first of all we get the time stamp... */ + pps_get_ts(&ts_assert); + + if (dev->cw == 0) + /* clear edge capture disabled */ + goto out_assert; + + /* try capture the clear edge */ + + /* We have to disable interrupts here. The idea is to prevent + * other interrupts on the same processor to introduce random + * lags while polling the port. Reading from IO port is known + * to take approximately 1us while other interrupt handlers can + * take much more potentially. + * + * Interrupts won't be disabled for a long time because the + * number of polls is limited by clear_wait parameter which is + * kept rather low. So it should never be an issue. + */ + local_irq_save(flags); + /* check the signal (no signal means the pulse is lost this time) */ + if (!signal_is_set(port)) { + local_irq_restore(flags); + dev_err(&dev->pps->dev, "lost the signal\n"); + goto out_assert; + } + + /* poll the port until the signal is unset */ + for (i = dev->cw; i; i--) + if (!signal_is_set(port)) { + pps_get_ts(&ts_clear); + local_irq_restore(flags); + dev->cw_err = 0; + goto out_both; + } + local_irq_restore(flags); + + /* timeout */ + dev->cw_err++; + if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) { + dev_err(&dev->pps->dev, "disabled clear edge capture after %d" + " timeouts\n", dev->cw_err); + dev->cw = 0; + dev->cw_err = 0; + } + +out_assert: + /* fire assert event */ + pps_event(dev->pps, &ts_assert, + PPS_CAPTUREASSERT, NULL); + return; + +out_both: + /* fire assert event */ + pps_event(dev->pps, &ts_assert, + PPS_CAPTUREASSERT, NULL); + /* fire clear event */ + pps_event(dev->pps, &ts_clear, + PPS_CAPTURECLEAR, NULL); + return; +} + +static void parport_attach(struct parport *port) +{ + struct pardev_cb pps_client_cb; + int index; + struct pps_client_pp *device; + struct pps_source_info info = { + .name = KBUILD_MODNAME, + .path = "", + .mode = PPS_CAPTUREBOTH | \ + PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ + PPS_ECHOASSERT | PPS_ECHOCLEAR | \ + PPS_CANWAIT | PPS_TSFMT_TSPEC, + .owner = THIS_MODULE, + .dev = NULL + }; + + if (clear_wait > CLEAR_WAIT_MAX) { + pr_err("clear_wait value should be not greater then %d\n", + CLEAR_WAIT_MAX); + return; + } + + device = kzalloc_obj(struct pps_client_pp); + if (!device) { + pr_err("memory allocation failed, not attaching\n"); + return; + } + + index = ida_alloc(&pps_client_index, GFP_KERNEL); + if (index < 0) + goto err_free_device; + + memset(&pps_client_cb, 0, sizeof(pps_client_cb)); + pps_client_cb.private = device; + pps_client_cb.irq_func = parport_irq; + pps_client_cb.flags = PARPORT_FLAG_EXCL; + device->pardev = parport_register_dev_model(port, + KBUILD_MODNAME, + &pps_client_cb, + index); + if (!device->pardev) { + pr_err("couldn't register with %s\n", port->name); + goto err_free_ida; + } + + if (parport_claim_or_block(device->pardev) < 0) { + pr_err("couldn't claim %s\n", port->name); + goto err_unregister_dev; + } + + device->pps = pps_register_source(&info, + PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR); + if (IS_ERR(device->pps)) { + pr_err("couldn't register PPS source\n"); + goto err_release_dev; + } + + device->cw = clear_wait; + + port->ops->enable_irq(port); + device->index = index; + + pr_info("attached to %s\n", port->name); + + return; + +err_release_dev: + parport_release(device->pardev); +err_unregister_dev: + parport_unregister_device(device->pardev); +err_free_ida: + ida_free(&pps_client_index, index); +err_free_device: + kfree(device); +} + +static void parport_detach(struct parport *port) +{ + struct pardevice *pardev = port->cad; + struct pps_client_pp *device; + + /* FIXME: oooh, this is ugly! */ + if (!pardev || strcmp(pardev->name, KBUILD_MODNAME)) + /* not our port */ + return; + + device = pardev->private; + + port->ops->disable_irq(port); + pps_unregister_source(device->pps); + parport_release(pardev); + parport_unregister_device(pardev); + ida_free(&pps_client_index, device->index); + kfree(device); +} + +static struct parport_driver pps_parport_driver = { + .name = KBUILD_MODNAME, + .match_port = parport_attach, + .detach = parport_detach, +}; +module_parport_driver(pps_parport_driver); + +MODULE_AUTHOR("Alexander Gordeev "); +MODULE_DESCRIPTION("parallel port PPS client"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig new file mode 100644 index 000000000..4ef02b3f2 --- /dev/null +++ b/drivers/pps/generators/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PPS generators configuration +# + +menuconfig PPS_GENERATOR + tristate "PPS generators support" + help + PPS generators are special hardware which are able to produce PPS + (Pulse Per Second) signals. + + To compile this driver as a module, choose M here: the module + will be called pps_gen_core. + +if PPS_GENERATOR + +config PPS_GENERATOR_DUMMY + tristate "Dummy PPS generator (Testing generator, use for debug)" + help + If you say yes here you get support for a PPS debugging generator + (which generates no PPS signal at all). + + This driver can also be built as a module. If so, the module + will be called pps_gen-dummy. + +config PPS_GENERATOR_TIO + tristate "TIO PPS signal generator" + depends on X86 && CPU_SUP_INTEL + help + If you say yes here you get support for a PPS TIO signal generator + which generates a pulse at a prescribed time based on the system clock. + It uses time translation and hrtimers to precisely generate a pulse. + This hardware is present on 2019 and newer Intel CPUs. However, this + driver is not useful without adding highly specialized hardware outside + the Linux system to observe these pulses. + + To compile this driver as a module, choose M here: the module + will be called pps_gen_tio. + + If unsure, say N. + +endif # PPS_GENERATOR diff --git a/drivers/pps/generators/Makefile b/drivers/pps/generators/Makefile new file mode 100644 index 000000000..5d38774b4 --- /dev/null +++ b/drivers/pps/generators/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for PPS generators. +# + +pps_gen_core-y := pps_gen.o sysfs.o +obj-$(CONFIG_PPS_GENERATOR) := pps_gen_core.o + +obj-$(CONFIG_PPS_GENERATOR_DUMMY) += pps_gen-dummy.o +obj-$(CONFIG_PPS_GENERATOR_TIO) += pps_gen_tio.o + +ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/generators/pps_gen-dummy.c b/drivers/pps/generators/pps_gen-dummy.c new file mode 100644 index 000000000..a4395543c --- /dev/null +++ b/drivers/pps/generators/pps_gen-dummy.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS dummy generator + * + * Copyright (C) 2024 Rodolfo Giometti + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct pps_gen_device *pps_gen; +static struct timer_list ktimer; + +static unsigned int get_random_delay(void) +{ + unsigned int delay = get_random_u8() & 0x0f; + + return (delay + 1) * HZ; +} + +/* + * The kernel timer + */ + +static void pps_gen_ktimer_event(struct timer_list *unused) +{ + pps_gen_event(pps_gen, PPS_GEN_EVENT_MISSEDPULSE, NULL); +} + +/* + * PPS Generator methods + */ + +static int pps_gen_dummy_get_time(struct pps_gen_device *pps_gen, + struct timespec64 *time) +{ + ktime_get_real_ts64(time); + return 0; +} + +static int pps_gen_dummy_enable(struct pps_gen_device *pps_gen, bool enable) +{ + if (enable) + mod_timer(&ktimer, jiffies + get_random_delay()); + else + timer_delete_sync(&ktimer); + + return 0; +} + +/* + * The PPS info struct + */ + +static const struct pps_gen_source_info pps_gen_dummy_info = { + .use_system_clock = true, + .get_time = pps_gen_dummy_get_time, + .enable = pps_gen_dummy_enable, +}; + +/* + * Module staff + */ + +static void __exit pps_gen_dummy_exit(void) +{ + timer_delete_sync(&ktimer); + pps_gen_unregister_source(pps_gen); +} + +static int __init pps_gen_dummy_init(void) +{ + pps_gen = pps_gen_register_source(&pps_gen_dummy_info); + if (IS_ERR(pps_gen)) + return PTR_ERR(pps_gen); + + timer_setup(&ktimer, pps_gen_ktimer_event, 0); + + return 0; +} + +module_init(pps_gen_dummy_init); +module_exit(pps_gen_dummy_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("LinuxPPS dummy generator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/pps_gen.c b/drivers/pps/generators/pps_gen.c new file mode 100644 index 000000000..5e207c75e --- /dev/null +++ b/drivers/pps/generators/pps_gen.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS generators core file + * + * Copyright (C) 2024 Rodolfo Giometti + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_DESCRIPTION("LinuxPPS generators support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/pps_gen_tio.c b/drivers/pps/generators/pps_gen_tio.c new file mode 100644 index 000000000..5088aaf1a --- /dev/null +++ b/drivers/pps/generators/pps_gen_tio.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel PPS signal Generator Driver + * + * Copyright (C) 2024 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TIOCTL 0x00 +#define TIOCOMPV 0x10 +#define TIOEC 0x30 + +/* Control Register */ +#define TIOCTL_EN BIT(0) +#define TIOCTL_DIR BIT(1) +#define TIOCTL_EP GENMASK(3, 2) +#define TIOCTL_EP_RISING_EDGE FIELD_PREP(TIOCTL_EP, 0) +#define TIOCTL_EP_FALLING_EDGE FIELD_PREP(TIOCTL_EP, 1) +#define TIOCTL_EP_TOGGLE_EDGE FIELD_PREP(TIOCTL_EP, 2) + +/* Safety time to set hrtimer early */ +#define SAFE_TIME_NS (10 * NSEC_PER_MSEC) + +#define MAGIC_CONST (NSEC_PER_SEC - SAFE_TIME_NS) +#define ART_HW_DELAY_CYCLES 2 + +struct pps_tio { + struct pps_gen_source_info gen_info; + struct pps_gen_device *pps_gen; + struct hrtimer timer; + void __iomem *base; + u32 prev_count; + spinlock_t lock; + struct device *dev; +}; + +static inline u32 pps_tio_read(u32 offset, struct pps_tio *tio) +{ + return readl(tio->base + offset); +} + +static inline void pps_ctl_write(u32 value, struct pps_tio *tio) +{ + writel(value, tio->base + TIOCTL); +} + +/* + * For COMPV register, It's safer to write + * higher 32-bit followed by lower 32-bit + */ +static inline void pps_compv_write(u64 value, struct pps_tio *tio) +{ + hi_lo_writeq(value, tio->base + TIOCOMPV); +} + +static inline ktime_t first_event(struct pps_tio *tio) +{ + return ktime_set(ktime_get_real_seconds() + 1, MAGIC_CONST); +} + +static u32 pps_tio_disable(struct pps_tio *tio) +{ + u32 ctrl; + + ctrl = pps_tio_read(TIOCTL, tio); + pps_compv_write(0, tio); + + ctrl &= ~TIOCTL_EN; + pps_ctl_write(ctrl, tio); + tio->pps_gen->enabled = false; + tio->prev_count = 0; + return ctrl; +} + +static void pps_tio_enable(struct pps_tio *tio) +{ + u32 ctrl; + + ctrl = pps_tio_read(TIOCTL, tio); + ctrl |= TIOCTL_EN; + pps_ctl_write(ctrl, tio); + tio->pps_gen->enabled = true; +} + +static void pps_tio_direction_output(struct pps_tio *tio) +{ + u32 ctrl; + + ctrl = pps_tio_disable(tio); + + /* + * We enable the device, be sure that the + * 'compare' value is invalid + */ + pps_compv_write(0, tio); + + ctrl &= ~(TIOCTL_DIR | TIOCTL_EP); + ctrl |= TIOCTL_EP_TOGGLE_EDGE; + pps_ctl_write(ctrl, tio); + pps_tio_enable(tio); +} + +static bool pps_generate_next_pulse(ktime_t expires, struct pps_tio *tio) +{ + u64 art; + + if (!ktime_real_to_base_clock(expires, CSID_X86_ART, &art)) { + pps_tio_disable(tio); + return false; + } + + pps_compv_write(art - ART_HW_DELAY_CYCLES, tio); + return true; +} + +static enum hrtimer_restart hrtimer_callback(struct hrtimer *timer) +{ + ktime_t expires, now; + u32 event_count; + struct pps_tio *tio = container_of(timer, struct pps_tio, timer); + + guard(spinlock)(&tio->lock); + + /* + * Check if any event is missed. + * If an event is missed, TIO will be disabled. + */ + event_count = pps_tio_read(TIOEC, tio); + if (tio->prev_count && tio->prev_count == event_count) + goto err; + tio->prev_count = event_count; + + expires = hrtimer_get_expires(timer); + + now = ktime_get_real(); + if (now - expires >= SAFE_TIME_NS) + goto err; + + tio->pps_gen->enabled = pps_generate_next_pulse(expires + SAFE_TIME_NS, tio); + if (!tio->pps_gen->enabled) + return HRTIMER_NORESTART; + + hrtimer_forward(timer, now, NSEC_PER_SEC / 2); + return HRTIMER_RESTART; + +err: + dev_err(tio->dev, "Event missed, Disabling Timed I/O"); + pps_tio_disable(tio); + pps_gen_event(tio->pps_gen, PPS_GEN_EVENT_MISSEDPULSE, NULL); + return HRTIMER_NORESTART; +} + +static int pps_tio_gen_enable(struct pps_gen_device *pps_gen, bool enable) +{ + struct pps_tio *tio = container_of(pps_gen->info, struct pps_tio, gen_info); + + if (!timekeeping_clocksource_has_base(CSID_X86_ART)) { + dev_err_once(tio->dev, "PPS cannot be used as clock is not related to ART"); + return -ENODEV; + } + + guard(spinlock_irqsave)(&tio->lock); + if (enable && !pps_gen->enabled) { + pps_tio_direction_output(tio); + hrtimer_start(&tio->timer, first_event(tio), HRTIMER_MODE_ABS); + } else if (!enable && pps_gen->enabled) { + hrtimer_cancel(&tio->timer); + pps_tio_disable(tio); + } + + return 0; +} + +static int pps_tio_get_time(struct pps_gen_device *pps_gen, + struct timespec64 *time) +{ + ktime_get_real_ts64(time); + return 0; +} + +static int pps_gen_tio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pps_tio *tio; + + if (!(cpu_feature_enabled(X86_FEATURE_TSC_KNOWN_FREQ) && + cpu_feature_enabled(X86_FEATURE_ART))) { + dev_warn(dev, "TSC/ART is not enabled"); + return -ENODEV; + } + + tio = devm_kzalloc(dev, sizeof(*tio), GFP_KERNEL); + if (!tio) + return -ENOMEM; + + tio->gen_info.use_system_clock = true; + tio->gen_info.enable = pps_tio_gen_enable; + tio->gen_info.get_time = pps_tio_get_time; + tio->gen_info.owner = THIS_MODULE; + + tio->pps_gen = pps_gen_register_source(&tio->gen_info); + if (IS_ERR(tio->pps_gen)) + return PTR_ERR(tio->pps_gen); + + tio->dev = dev; + tio->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tio->base)) + return PTR_ERR(tio->base); + + pps_tio_disable(tio); + hrtimer_setup(&tio->timer, hrtimer_callback, CLOCK_REALTIME, + HRTIMER_MODE_ABS); + spin_lock_init(&tio->lock); + platform_set_drvdata(pdev, tio); + + return 0; +} + +static void pps_gen_tio_remove(struct platform_device *pdev) +{ + struct pps_tio *tio = platform_get_drvdata(pdev); + + hrtimer_cancel(&tio->timer); + pps_tio_disable(tio); + pps_gen_unregister_source(tio->pps_gen); +} + +static const struct acpi_device_id intel_pmc_tio_acpi_match[] = { + { "INTC1021" }, + { "INTC1022" }, + { "INTC1023" }, + { "INTC1024" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, intel_pmc_tio_acpi_match); + +static struct platform_driver pps_gen_tio_driver = { + .probe = pps_gen_tio_probe, + .remove = pps_gen_tio_remove, + .driver = { + .name = "intel-pps-gen-tio", + .acpi_match_table = intel_pmc_tio_acpi_match, + }, +}; +module_platform_driver(pps_gen_tio_driver); + +MODULE_AUTHOR("Christopher Hall "); +MODULE_AUTHOR("Lakshmi Sowjanya D "); +MODULE_AUTHOR("Pandith N "); +MODULE_AUTHOR("Thejesh Reddy T R "); +MODULE_AUTHOR("Subramanian Mohan "); +MODULE_DESCRIPTION("Intel PMC Time-Aware IO Generator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/sysfs.c b/drivers/pps/generators/sysfs.c new file mode 100644 index 000000000..6d6bc0006 --- /dev/null +++ b/drivers/pps/generators/sysfs.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS generators sysfs support + * + * Copyright (C) 2024 Rodolfo Giometti + */ + +#include +#include +#include +#include + +/* + * Attribute functions + */ + +static ssize_t system_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", pps_gen->info->use_system_clock); +} +static DEVICE_ATTR_RO(system); + +static ssize_t time_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + struct timespec64 time; + int ret; + + ret = pps_gen->info->get_time(pps_gen, &time); + if (ret) + return ret; + + return sysfs_emit(buf, "%llu %09lu\n", time.tv_sec, time.tv_nsec); +} +static DEVICE_ATTR_RO(time); + +static ssize_t enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + bool status; + int ret; + + ret = kstrtobool(buf, &status); + if (ret) + return ret; + + ret = pps_gen->info->enable(pps_gen, status); + if (ret) + return ret; + pps_gen->enabled = status; + + return count; +} +static DEVICE_ATTR_WO(enable); + +static struct attribute *pps_gen_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_time.attr, + &dev_attr_system.attr, + NULL, +}; + +static const struct attribute_group pps_gen_group = { + .attrs = pps_gen_attrs, +}; + +const struct attribute_group *pps_gen_groups[] = { + &pps_gen_group, + NULL, +}; diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c new file mode 100644 index 000000000..1bf0335a1 --- /dev/null +++ b/drivers/pps/kapi.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * kernel API + * + * Copyright (C) 2005-2009 Rodolfo Giometti + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kc.h" + +/* + * Local functions + */ + +static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset) +{ + ts->nsec += offset->nsec; + while (ts->nsec >= NSEC_PER_SEC) { + ts->nsec -= NSEC_PER_SEC; + ts->sec++; + } + while (ts->nsec < 0) { + ts->nsec += NSEC_PER_SEC; + ts->sec--; + } + ts->sec += offset->sec; +} + +static void pps_echo_client_default(struct pps_device *pps, int event, + void *data) +{ + dev_info(&pps->dev, "echo %s %s\n", + event & PPS_CAPTUREASSERT ? "assert" : "", + event & PPS_CAPTURECLEAR ? "clear" : ""); +} + +/* + * Exported functions + */ + +/* pps_register_source - add a PPS source in the system + * @info: the PPS info struct + * @default_params: the default PPS parameters of the new source + * + * This function is used to add a new PPS source in the system. The new + * source is described by info's fields and it will have, as default PPS + * parameters, the ones specified into default_params. + * + * The function returns, in case of success, the PPS device. Otherwise + * ERR_PTR(errno). + */ + +struct pps_device *pps_register_source(struct pps_source_info *info, + int default_params) +{ + struct pps_device *pps; + int err; + + /* Sanity checks */ + if ((info->mode & default_params) != default_params) { + pr_err("%s: unsupported default parameters\n", + info->name); + err = -EINVAL; + goto pps_register_source_exit; + } + if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { + pr_err("%s: unspecified time format\n", + info->name); + err = -EINVAL; + goto pps_register_source_exit; + } + + /* Allocate memory for the new PPS source struct */ + pps = kzalloc_obj(struct pps_device); + if (pps == NULL) { + err = -ENOMEM; + goto pps_register_source_exit; + } + + /* These initializations must be done before calling idr_alloc() + * in order to avoid reces into pps_event(). + */ + pps->params.api_version = PPS_API_VERS; + pps->params.mode = default_params; + pps->info = *info; + + /* check for default echo function */ + if ((pps->info.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) && + pps->info.echo == NULL) + pps->info.echo = pps_echo_client_default; + + init_waitqueue_head(&pps->queue); + spin_lock_init(&pps->lock); + + /* Create the char device */ + err = pps_register_cdev(pps); + if (err < 0) { + pr_err("%s: unable to create char device\n", + info->name); + goto pps_register_source_exit; + } + + dev_dbg(&pps->dev, "new PPS source %s\n", info->name); + + return pps; + +pps_register_source_exit: + pr_err("%s: unable to register source\n", info->name); + + return ERR_PTR(err); +} +EXPORT_SYMBOL(pps_register_source); + +/* pps_unregister_source - remove a PPS source from the system + * @pps: the PPS source + * + * This function is used to remove a previously registered PPS source from + * the system. + */ + +void pps_unregister_source(struct pps_device *pps) +{ + pps_kc_remove(pps); + pps_unregister_cdev(pps); + + /* don't have to kfree(pps) here because it will be done on + * device destruction */ +} +EXPORT_SYMBOL(pps_unregister_source); + +/* pps_event - register a PPS event into the system + * @pps: the PPS device + * @ts: the event timestamp + * @event: the event type + * @data: userdef pointer + * + * This function is used by each PPS client in order to register a new + * PPS event into the system (it's usually called inside an IRQ handler). + * + * If an echo function is associated with the PPS device it will be called + * as: + * pps->info.echo(pps, event, data); + */ +void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, + void *data) +{ + unsigned long flags; + int captured = 0; + struct pps_ktime ts_real = { .sec = 0, .nsec = 0, .flags = 0 }; + + /* check event type */ + BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0); + + dev_dbg(&pps->dev, "PPS event at %ptSp\n", &ts->ts_real); + + timespec_to_pps_ktime(&ts_real, ts->ts_real); + + spin_lock_irqsave(&pps->lock, flags); + + /* Must call the echo function? */ + if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR))) + pps->info.echo(pps, event, data); + + /* Check the event */ + pps->current_mode = pps->params.mode; + if (event & pps->params.mode & PPS_CAPTUREASSERT) { + /* We have to add an offset? */ + if (pps->params.mode & PPS_OFFSETASSERT) + pps_add_offset(&ts_real, + &pps->params.assert_off_tu); + + /* Save the time stamp */ + pps->assert_tu = ts_real; + pps->assert_sequence++; + dev_dbg(&pps->dev, "capture assert seq #%u\n", + pps->assert_sequence); + + captured = ~0; + } + if (event & pps->params.mode & PPS_CAPTURECLEAR) { + /* We have to add an offset? */ + if (pps->params.mode & PPS_OFFSETCLEAR) + pps_add_offset(&ts_real, + &pps->params.clear_off_tu); + + /* Save the time stamp */ + pps->clear_tu = ts_real; + pps->clear_sequence++; + dev_dbg(&pps->dev, "capture clear seq #%u\n", + pps->clear_sequence); + + captured = ~0; + } + + pps_kc_event(pps, ts, event); + + /* Wake up if captured something */ + if (captured) { + pps->last_ev++; + wake_up_interruptible_all(&pps->queue); + + kill_fasync(&pps->async_queue, SIGIO, POLL_IN); + } + + spin_unlock_irqrestore(&pps->lock, flags); +} +EXPORT_SYMBOL(pps_event); diff --git a/drivers/pps/kc.c b/drivers/pps/kc.c new file mode 100644 index 000000000..4f8fffa7e --- /dev/null +++ b/drivers/pps/kc.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS kernel consumer API + * + * Copyright (C) 2009-2010 Alexander Gordeev + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "kc.h" + +/* + * Global variables + */ + +/* state variables to bind kernel consumer */ +static DEFINE_SPINLOCK(pps_kc_hardpps_lock); +/* PPS API (RFC 2783): current source and mode for kernel consumer */ +static struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */ +static int pps_kc_hardpps_mode; /* mode bits for kernel consumer */ + +/* pps_kc_bind - control PPS kernel consumer binding + * @pps: the PPS source + * @bind_args: kernel consumer bind parameters + * + * This function is used to bind or unbind PPS kernel consumer according to + * supplied parameters. Should not be called in interrupt context. + */ +int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) +{ + /* Check if another consumer is already bound */ + spin_lock_irq(&pps_kc_hardpps_lock); + + /* + * Don't allow PPS_KC_BIND on a removed device. + */ + if (pps->kc_removed) { + spin_unlock_irq(&pps_kc_hardpps_lock); + return -ENODEV; + } + + if (bind_args->edge == 0) + if (pps_kc_hardpps_dev == pps) { + pps_kc_hardpps_mode = 0; + pps_kc_hardpps_dev = NULL; + spin_unlock_irq(&pps_kc_hardpps_lock); + dev_info(&pps->dev, "unbound kernel" + " consumer\n"); + } else { + spin_unlock_irq(&pps_kc_hardpps_lock); + dev_err(&pps->dev, "selected kernel consumer" + " is not bound\n"); + return -EINVAL; + } + else + if (pps_kc_hardpps_dev == NULL || + pps_kc_hardpps_dev == pps) { + pps_kc_hardpps_mode = bind_args->edge; + pps_kc_hardpps_dev = pps; + spin_unlock_irq(&pps_kc_hardpps_lock); + dev_info(&pps->dev, "bound kernel consumer: " + "edge=0x%x\n", bind_args->edge); + } else { + spin_unlock_irq(&pps_kc_hardpps_lock); + dev_err(&pps->dev, "another kernel consumer" + " is already bound\n"); + return -EINVAL; + } + + return 0; +} + +/* pps_kc_remove - unbind kernel consumer on PPS source removal + * @pps: the PPS source + * + * This function is used to disable kernel consumer on PPS source removal + * if this source was bound to PPS kernel consumer. Can be called on any + * source safely. Should not be called in interrupt context. + */ +void pps_kc_remove(struct pps_device *pps) +{ + spin_lock_irq(&pps_kc_hardpps_lock); + + pps->kc_removed = true; + if (pps == pps_kc_hardpps_dev) { + pps_kc_hardpps_mode = 0; + pps_kc_hardpps_dev = NULL; + spin_unlock_irq(&pps_kc_hardpps_lock); + dev_info(&pps->dev, "unbound kernel consumer" + " on device removal\n"); + } else + spin_unlock_irq(&pps_kc_hardpps_lock); +} + +/* pps_kc_event - call hardpps() on PPS event + * @pps: the PPS source + * @ts: PPS event timestamp + * @event: PPS event edge + * + * This function calls hardpps() when an event from bound PPS source occurs. + */ +void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts, + int event) +{ + unsigned long flags; + + /* Pass some events to kernel consumer if activated */ + spin_lock_irqsave(&pps_kc_hardpps_lock, flags); + if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode) + hardpps(&ts->ts_real, &ts->ts_raw); + spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags); +} diff --git a/drivers/pps/kc.h b/drivers/pps/kc.h new file mode 100644 index 000000000..247305594 --- /dev/null +++ b/drivers/pps/kc.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * PPS kernel consumer API header + * + * Copyright (C) 2009-2010 Alexander Gordeev + */ + +#ifndef LINUX_PPS_KC_H +#define LINUX_PPS_KC_H + +#include +#include + +#ifdef CONFIG_NTP_PPS + +extern int pps_kc_bind(struct pps_device *pps, + struct pps_bind_args *bind_args); +extern void pps_kc_remove(struct pps_device *pps); +extern void pps_kc_event(struct pps_device *pps, + struct pps_event_time *ts, int event); + + +#else /* CONFIG_NTP_PPS */ + +static inline int pps_kc_bind(struct pps_device *pps, + struct pps_bind_args *bind_args) { return -EOPNOTSUPP; } +static inline void pps_kc_remove(struct pps_device *pps) {} +static inline void pps_kc_event(struct pps_device *pps, + struct pps_event_time *ts, int event) {} + +#endif /* CONFIG_NTP_PPS */ + +#endif /* LINUX_PPS_KC_H */ diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c new file mode 100644 index 000000000..7ecdd774a --- /dev/null +++ b/drivers/pps/pps.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS core file + * + * Copyright (C) 2005-2009 Rodolfo Giometti + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kc.h" + +/* + * Local variables + */ + +static int pps_major; +static const struct class pps_class = { + .name = "pps", + .dev_groups = pps_groups +}; + +static DEFINE_MUTEX(pps_idr_lock); +static DEFINE_IDR(pps_idr); + +/* + * Char device methods + */ + +static __poll_t pps_cdev_poll(struct file *file, poll_table *wait) +{ + struct pps_device *pps = file->private_data; + + poll_wait(file, &pps->queue, wait); + + if (pps->last_fetched_ev == pps->last_ev) + return 0; + + return EPOLLIN | EPOLLRDNORM; +} + +static int pps_cdev_fasync(int fd, struct file *file, int on) +{ + struct pps_device *pps = file->private_data; + return fasync_helper(fd, file, on, &pps->async_queue); +} + +static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata) +{ + unsigned int ev = pps->last_ev; + int err = 0; + + /* Manage the timeout */ + if (fdata->timeout.flags & PPS_TIME_INVALID) + err = wait_event_interruptible(pps->queue, + ev != pps->last_ev); + else { + struct timespec64 ts; + unsigned long ticks; + + dev_dbg(&pps->dev, "timeout %lld.%09d\n", + (long long) fdata->timeout.sec, + fdata->timeout.nsec); + + if (fdata->timeout.sec < 0) + return -ETIMEDOUT; + + ts.tv_sec = fdata->timeout.sec; + ts.tv_nsec = fdata->timeout.nsec; + ticks = timespec64_to_jiffies(&ts); + + if (ticks != 0) { + err = wait_event_interruptible_timeout( + pps->queue, + ev != pps->last_ev, + ticks); + if (err == 0) + return -ETIMEDOUT; + } + } + + /* Check for pending signals */ + if (err == -ERESTARTSYS) { + dev_dbg(&pps->dev, "pending signal caught\n"); + return -EINTR; + } + + return 0; +} + +static long pps_cdev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct pps_device *pps = file->private_data; + struct pps_kparams params; + void __user *uarg = (void __user *) arg; + int __user *iuarg = (int __user *) arg; + int err; + + switch (cmd) { + case PPS_GETPARAMS: + dev_dbg(&pps->dev, "PPS_GETPARAMS\n"); + + spin_lock_irq(&pps->lock); + + /* Get the current parameters */ + params = pps->params; + + spin_unlock_irq(&pps->lock); + + err = copy_to_user(uarg, ¶ms, sizeof(struct pps_kparams)); + if (err) + return -EFAULT; + + break; + + case PPS_SETPARAMS: + dev_dbg(&pps->dev, "PPS_SETPARAMS\n"); + + /* Check the capabilities */ + if (!capable(CAP_SYS_TIME)) + return -EPERM; + + err = copy_from_user(¶ms, uarg, sizeof(struct pps_kparams)); + if (err) + return -EFAULT; + if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { + dev_dbg(&pps->dev, "capture mode unspecified (%x)\n", + params.mode); + return -EINVAL; + } + + /* Check for supported capabilities */ + if ((params.mode & ~pps->info.mode) != 0) { + dev_dbg(&pps->dev, "unsupported capabilities (%x)\n", + params.mode); + return -EINVAL; + } + + spin_lock_irq(&pps->lock); + + /* Save the new parameters */ + pps->params = params; + + /* Restore the read only parameters */ + if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { + /* section 3.3 of RFC 2783 interpreted */ + dev_dbg(&pps->dev, "time format unspecified (%x)\n", + params.mode); + pps->params.mode |= PPS_TSFMT_TSPEC; + } + if (pps->info.mode & PPS_CANWAIT) + pps->params.mode |= PPS_CANWAIT; + pps->params.api_version = PPS_API_VERS; + + /* + * Clear unused fields of pps_kparams to avoid leaking + * uninitialized data of the PPS_SETPARAMS caller via + * PPS_GETPARAMS + */ + pps->params.assert_off_tu.flags = 0; + pps->params.clear_off_tu.flags = 0; + + spin_unlock_irq(&pps->lock); + + break; + + case PPS_GETCAP: + dev_dbg(&pps->dev, "PPS_GETCAP\n"); + + err = put_user(pps->info.mode, iuarg); + if (err) + return -EFAULT; + + break; + + case PPS_FETCH: { + struct pps_fdata fdata; + + dev_dbg(&pps->dev, "PPS_FETCH\n"); + + err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata)); + if (err) + return -EFAULT; + + err = pps_cdev_pps_fetch(pps, &fdata); + if (err) + return err; + + /* Return the fetched timestamp and save last fetched event */ + spin_lock_irq(&pps->lock); + + pps->last_fetched_ev = pps->last_ev; + + fdata.info.assert_sequence = pps->assert_sequence; + fdata.info.clear_sequence = pps->clear_sequence; + fdata.info.assert_tu = pps->assert_tu; + fdata.info.clear_tu = pps->clear_tu; + fdata.info.current_mode = pps->current_mode; + + spin_unlock_irq(&pps->lock); + + err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata)); + if (err) + return -EFAULT; + + break; + } + case PPS_KC_BIND: { + struct pps_bind_args bind_args; + + dev_dbg(&pps->dev, "PPS_KC_BIND\n"); + + /* Check the capabilities */ + if (!capable(CAP_SYS_TIME)) + return -EPERM; + + if (copy_from_user(&bind_args, uarg, + sizeof(struct pps_bind_args))) + return -EFAULT; + + /* Check for supported capabilities */ + if ((bind_args.edge & ~pps->info.mode) != 0) { + dev_err(&pps->dev, "unsupported capabilities (%x)\n", + bind_args.edge); + return -EINVAL; + } + + /* Validate parameters roughly */ + if (bind_args.tsformat != PPS_TSFMT_TSPEC || + (bind_args.edge & ~PPS_CAPTUREBOTH) != 0 || + bind_args.consumer != PPS_KC_HARDPPS) { + dev_err(&pps->dev, "invalid kernel consumer bind" + " parameters (%x)\n", bind_args.edge); + return -EINVAL; + } + + err = pps_kc_bind(pps, &bind_args); + if (err < 0) + return err; + + break; + } + default: + return -ENOTTY; + } + + return 0; +} + +#ifdef CONFIG_COMPAT +static long pps_cdev_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct pps_device *pps = file->private_data; + void __user *uarg = (void __user *) arg; + + cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *)); + + if (cmd == PPS_FETCH) { + struct pps_fdata_compat compat; + struct pps_fdata fdata; + int err; + + dev_dbg(&pps->dev, "PPS_FETCH\n"); + + err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat)); + if (err) + return -EFAULT; + + memcpy(&fdata.timeout, &compat.timeout, + sizeof(struct pps_ktime_compat)); + + err = pps_cdev_pps_fetch(pps, &fdata); + if (err) + return err; + + /* Return the fetched timestamp and save last fetched event */ + spin_lock_irq(&pps->lock); + + pps->last_fetched_ev = pps->last_ev; + + compat.info.assert_sequence = pps->assert_sequence; + compat.info.clear_sequence = pps->clear_sequence; + compat.info.current_mode = pps->current_mode; + + memcpy(&compat.info.assert_tu, &pps->assert_tu, + sizeof(struct pps_ktime_compat)); + memcpy(&compat.info.clear_tu, &pps->clear_tu, + sizeof(struct pps_ktime_compat)); + + spin_unlock_irq(&pps->lock); + + return copy_to_user(uarg, &compat, + sizeof(struct pps_fdata_compat)) ? -EFAULT : 0; + } + + return pps_cdev_ioctl(file, cmd, arg); +} +#else +#define pps_cdev_compat_ioctl NULL +#endif + +static struct pps_device *pps_idr_get(unsigned long id) +{ + struct pps_device *pps; + + mutex_lock(&pps_idr_lock); + pps = idr_find(&pps_idr, id); + if (pps) + get_device(&pps->dev); + + mutex_unlock(&pps_idr_lock); + return pps; +} + +static int pps_cdev_open(struct inode *inode, struct file *file) +{ + struct pps_device *pps = pps_idr_get(iminor(inode)); + + if (!pps) + return -ENODEV; + + file->private_data = pps; + return 0; +} + +static int pps_cdev_release(struct inode *inode, struct file *file) +{ + struct pps_device *pps = file->private_data; + + WARN_ON(pps->id != iminor(inode)); + put_device(&pps->dev); + return 0; +} + +/* + * Char device stuff + */ + +static const struct file_operations pps_cdev_fops = { + .owner = THIS_MODULE, + .poll = pps_cdev_poll, + .fasync = pps_cdev_fasync, + .compat_ioctl = pps_cdev_compat_ioctl, + .unlocked_ioctl = pps_cdev_ioctl, + .open = pps_cdev_open, + .release = pps_cdev_release, +}; + +static void pps_device_destruct(struct device *dev) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + pr_debug("deallocating pps%d\n", pps->id); + kfree(pps); +} + +int pps_register_cdev(struct pps_device *pps) +{ + int err; + + mutex_lock(&pps_idr_lock); + /* + * Get new ID for the new PPS source. After idr_alloc() calling + * the new source will be freely available into the kernel. + */ + err = idr_alloc(&pps_idr, pps, 0, PPS_MAX_SOURCES, GFP_KERNEL); + if (err < 0) { + if (err == -ENOSPC) { + pr_err("%s: too many PPS sources in the system\n", + pps->info.name); + err = -EBUSY; + } + kfree(pps); + goto out_unlock; + } + pps->id = err; + + pps->dev.class = &pps_class; + pps->dev.parent = pps->info.dev; + pps->dev.devt = MKDEV(pps_major, pps->id); + dev_set_drvdata(&pps->dev, pps); + dev_set_name(&pps->dev, "pps%d", pps->id); + pps->dev.release = pps_device_destruct; + err = device_register(&pps->dev); + if (err) + goto free_idr; + + pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, pps_major, + pps->id); + + get_device(&pps->dev); + mutex_unlock(&pps_idr_lock); + return 0; + +free_idr: + idr_remove(&pps_idr, pps->id); + put_device(&pps->dev); +out_unlock: + mutex_unlock(&pps_idr_lock); + return err; +} + +void pps_unregister_cdev(struct pps_device *pps) +{ + pr_debug("unregistering pps%d\n", pps->id); + pps->lookup_cookie = NULL; + device_destroy(&pps_class, pps->dev.devt); + + /* Now we can release the ID for re-use */ + mutex_lock(&pps_idr_lock); + idr_remove(&pps_idr, pps->id); + put_device(&pps->dev); + mutex_unlock(&pps_idr_lock); +} + +/* + * Look up a pps device by magic cookie. + * The cookie is usually a pointer to some enclosing device, but this + * code doesn't care; you should never be dereferencing it. + * + * This is a bit of a kludge that is currently used only by the PPS + * serial line discipline. It may need to be tweaked when a second user + * is found. + * + * There is no function interface for setting the lookup_cookie field. + * It's initialized to NULL when the pps device is created, and if a + * client wants to use it, just fill it in afterward. + * + * The cookie is automatically set to NULL in pps_unregister_source() + * so that it will not be used again, even if the pps device cannot + * be removed from the idr due to pending references holding the minor + * number in use. + * + * Since pps_idr holds a reference to the device, the returned + * pps_device is guaranteed to be valid until pps_unregister_cdev() is + * called on it. But after calling pps_unregister_cdev(), it may be + * freed at any time. + */ +struct pps_device *pps_lookup_dev(void const *cookie) +{ + struct pps_device *pps; + unsigned id; + + rcu_read_lock(); + idr_for_each_entry(&pps_idr, pps, id) + if (cookie == pps->lookup_cookie) + break; + rcu_read_unlock(); + return pps; +} +EXPORT_SYMBOL(pps_lookup_dev); + +/* + * Module stuff + */ + +static void __exit pps_exit(void) +{ + class_unregister(&pps_class); + __unregister_chrdev(pps_major, 0, PPS_MAX_SOURCES, "pps"); +} + +static int __init pps_init(void) +{ + int err; + + err = class_register(&pps_class); + if (err) { + pr_err("failed to register class\n"); + return err; + } + + pps_major = __register_chrdev(0, 0, PPS_MAX_SOURCES, "pps", + &pps_cdev_fops); + if (pps_major < 0) { + pr_err("failed to allocate char device region\n"); + goto remove_class; + } + + pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS); + pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti " + "\n", PPS_VERSION); + + return 0; + +remove_class: + class_unregister(&pps_class); + return pps_major; +} + +subsys_initcall(pps_init); +module_exit(pps_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/sysfs.c b/drivers/pps/sysfs.c new file mode 100644 index 000000000..134bc33f6 --- /dev/null +++ b/drivers/pps/sysfs.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS sysfs support + * + * Copyright (C) 2007-2009 Rodolfo Giometti + */ + + +#include +#include +#include +#include + +/* + * Attribute functions + */ + +static ssize_t assert_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + if (!(pps->info.mode & PPS_CAPTUREASSERT)) + return 0; + + return sprintf(buf, "%lld.%09d#%d\n", + (long long) pps->assert_tu.sec, pps->assert_tu.nsec, + pps->assert_sequence); +} +static DEVICE_ATTR_RO(assert); + +static ssize_t clear_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + if (!(pps->info.mode & PPS_CAPTURECLEAR)) + return 0; + + return sprintf(buf, "%lld.%09d#%d\n", + (long long) pps->clear_tu.sec, pps->clear_tu.nsec, + pps->clear_sequence); +} +static DEVICE_ATTR_RO(clear); + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + return sprintf(buf, "%4x\n", pps->info.mode); +} +static DEVICE_ATTR_RO(mode); + +static ssize_t echo_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", !!pps->info.echo); +} +static DEVICE_ATTR_RO(echo); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", pps->info.name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t path_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_device *pps = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", pps->info.path); +} +static DEVICE_ATTR_RO(path); + +static struct attribute *pps_attrs[] = { + &dev_attr_assert.attr, + &dev_attr_clear.attr, + &dev_attr_mode.attr, + &dev_attr_echo.attr, + &dev_attr_name.attr, + &dev_attr_path.attr, + NULL, +}; + +static const struct attribute_group pps_group = { + .attrs = pps_attrs, +}; + +const struct attribute_group *pps_groups[] = { + &pps_group, + NULL, +}; -- cgit v1.3.1