diff --git a/Documentation/ABI/testing/sysfs-pps b/Documentation/ABI/testing/sysfs-pps new file mode 100644 index 0000000..25028c7 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-pps @@ -0,0 +1,73 @@ +What: /sys/class/pps/ +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ directory will contain files and + directories that will provide a unified interface to + the PPS sources. + +What: /sys/class/pps/ppsX/ +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/ directory is related to X-th + PPS source into the system. Each directory will + contain files to manage and control its PPS source. + +What: /sys/class/pps/ppsX/assert +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/assert file reports the assert events + and the assert sequence number of the X-th source in the form: + + .# + + If the source has no assert events the content of this file + is empty. + +What: /sys/class/pps/ppsX/clear +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/clear file reports the clear events + and the clear sequence number of the X-th source in the form: + + .# + + If the source has no clear events the content of this file + is empty. + +What: /sys/class/pps/ppsX/mode +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/mode file reports the functioning + mode of the X-th source in hexadecimal encoding. + + Please, refer to linux/include/linux/pps.h for further + info. + +What: /sys/class/pps/ppsX/echo +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/echo file reports if the X-th does + or does not support an "echo" function. + +What: /sys/class/pps/ppsX/name +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/name file reports the name of the + X-th source. + +What: /sys/class/pps/ppsX/path +Date: February 2008 +Contact: Rodolfo Giometti +Description: + The /sys/class/pps/ppsX/path file reports the path name of + the device connected with the X-th source. + + If the source is not connected with any device the content + of this file is empty. diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index b880ce5..fdf44d0 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -147,6 +147,8 @@ Code Seq# Include File Comments 'p' 40-7F linux/nvram.h 'p' 80-9F user-space parport +'p' a1-a4 linux/pps.h LinuxPPS + 'q' 00-1F linux/serio.h 'q' 80-FF Internet PhoneJACK, Internet LineJACK diff --git a/Documentation/pps/Makefile b/Documentation/pps/Makefile new file mode 100644 index 0000000..9841e6b --- /dev/null +++ b/Documentation/pps/Makefile @@ -0,0 +1,28 @@ +TARGETS = ppstest ppsldisc + +CFLAGS += -Wall -O2 -D_GNU_SOURCE +CFLAGS += -I . +CFLAGS += -ggdb +CFLAGS += -D__N_PPS=$(shell awk '/N_PPS/ {print $$3}' ../../include/linux/tty.h) + +# -- Actions section -- + +.PHONY : all depend dep + +all : .depend $(TARGETS) + +.depend depend dep : + $(CC) $(CFLAGS) -M $(TARGETS:=.c) > .depend + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif + + +# -- Clean section -- + +.PHONY : clean + +clean : + rm -f *.o *~ core .depend + rm -f ${TARGETS} diff --git a/Documentation/pps/pps.txt b/Documentation/pps/pps.txt new file mode 100644 index 0000000..125f4ab --- /dev/null +++ b/Documentation/pps/pps.txt @@ -0,0 +1,172 @@ + + PPS - Pulse Per Second + ---------------------- + +(C) Copyright 2007 Rodolfo Giometti + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + + + +Overview +-------- + +LinuxPPS provides a programming interface (API) to define in the +system several PPS sources. + +PPS means "pulse per second" and a PPS source is just a device which +provides a high precision signal each second so that an application +can use it to adjust system clock time. + +A PPS source can be connected to a serial port (usually to the Data +Carrier Detect pin) or to a parallel port (ACK-pin) or to a special +CPU's GPIOs (this is the common case in embedded systems) but in each +case when a new pulse arrives the system must apply to it a timestamp +and record it for userland. + +Common use is the combination of the NTPD as userland program, with a +GPS receiver as PPS source, to obtain a wallclock-time with +sub-millisecond synchronisation to UTC. + + +RFC considerations +------------------ + +While implementing a PPS API as RFC 2783 defines and using an embedded +CPU GPIO-Pin as physical link to the signal, I encountered a deeper +problem: + + At startup it needs a file descriptor as argument for the function + time_pps_create(). + +This implies that the source has a /dev/... entry. This assumption is +ok for the serial and parallel port, where you can do something +useful besides(!) the gathering of timestamps as it is the central +task for a PPS-API. But this assumption does not work for a single +purpose GPIO line. In this case even basic file-related functionality +(like read() and write()) makes no sense at all and should not be a +precondition for the use of a PPS-API. + +The problem can be simply solved if you consider that a PPS source is +not always connected with a GPS data source. + +So your programs should check if the GPS data source (the serial port +for instance) is a PPS source too, and if not they should provide the +possibility to open another device as PPS source. + +In LinuxPPS the PPS sources are simply char devices usually mapped +into files /dev/pps0, /dev/pps1, etc.. + + +Coding example +-------------- + +To register a PPS source into the kernel you should define a struct +pps_source_info_s as follows: + + static struct pps_source_info pps_ktimer_info = { + .name = "ktimer", + .path = "", + .mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | \ + PPS_ECHOASSERT | \ + PPS_CANWAIT | PPS_TSFMT_TSPEC, + .echo = pps_ktimer_echo, + .owner = THIS_MODULE, + }; + +and then calling the function pps_register_source() in your +intialization routine as follows: + + source = pps_register_source(&pps_ktimer_info, + PPS_CAPTUREASSERT | PPS_OFFSETASSERT); + +The pps_register_source() prototype is: + + int pps_register_source(struct pps_source_info_s *info, int default_params) + +where "info" is a pointer to a structure that describes a particular +PPS source, "default_params" tells the system what the initial default +parameters for the device should be (it is obvious that these parameters +must be a subset of ones defined in the struct +pps_source_info_s which describe the capabilities of the driver). + +Once you have registered a new PPS source into the system you can +signal an assert event (for example in the interrupt handler routine) +just using: + + pps_event(source, &ts, PPS_CAPTUREASSERT, ptr) + +where "ts" is the event's timestamp. + +The same function may also run the defined echo function +(pps_ktimer_echo(), passing to it the "ptr" pointer) if the user +asked for that... etc.. + +Please see the file drivers/pps/clients/ktimer.c for example code. + + +SYSFS support +------------- + +If the SYSFS filesystem is enabled in the kernel it provides a new class: + + $ ls /sys/class/pps/ + pps0/ pps1/ pps2/ + +Every directory is the ID of a PPS sources defined in the system and +inside you find several files: + + $ ls /sys/class/pps/pps0/ + assert clear echo mode name path subsystem@ uevent + +Inside each "assert" and "clear" file you can find the timestamp and a +sequence number: + + $ cat /sys/class/pps/pps0/assert + 1170026870.983207967#8 + +Where before the "#" is the timestamp in seconds; after it is the +sequence number. Other files are: + +* echo: reports if the PPS source has an echo function or not; + +* mode: reports available PPS functioning modes; + +* name: reports the PPS source's name; + +* path: reports the PPS source's device path, that is the device the + PPS source is connected to (if it exists). + + +Testing the PPS support +----------------------- + +In order to test the PPS support even without specific hardware you can use +the ktimer driver (see the client subsection in the PPS configuration menu) +and the userland tools provided into Documentaion/pps/ directory. + +Once you have enabled the compilation of ktimer just modprobe it (if +not statically compiled): + + # modprobe ktimer + +and the run ppstest as follow: + + $ ./ppstest /dev/pps0 + trying PPS source "/dev/pps1" + found PPS source "/dev/pps1" + ok, found 1 source(s), now start fetching data... + source 0 - assert 1186592699.388832443, sequence: 364 - clear 0.000000000, sequence: 0 + source 0 - assert 1186592700.388931295, sequence: 365 - clear 0.000000000, sequence: 0 + source 0 - assert 1186592701.389032765, sequence: 366 - clear 0.000000000, sequence: 0 + +Please, note that to compile userland programs you need the file timepps.h +(see Documentation/pps/). diff --git a/Documentation/pps/ppsfind b/Documentation/pps/ppsfind new file mode 100644 index 0000000..93c0e17 --- /dev/null +++ b/Documentation/pps/ppsfind @@ -0,0 +1,17 @@ +#!/bin/sh + +SYS="/sys/class/pps/" + +if [ $# -lt 1 ] ; then + echo "usage: ppsfind " >&2 + exit 1 +fi + +for d in $(ls $SYS) ; do + if grep $1 $SYS/$d/name >& /dev/null || \ + grep $1 $SYS/$d/path >& /dev/null ; then + echo "$d: name=$(cat $SYS/$d/name) path=$(cat $SYS/$d/path)" + fi +done + +exit 0 diff --git a/Documentation/pps/ppsldisc.c b/Documentation/pps/ppsldisc.c new file mode 100644 index 0000000..036e13b --- /dev/null +++ b/Documentation/pps/ppsldisc.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef N_PPS +#define N_PPS __N_PPS +#endif + +void usage(char *name) +{ + fprintf(stderr, "usage: %s \n", name); + + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) +{ + int fd; + int ldisc = N_PPS; + int ret; + + if (argc < 2) + usage(argv[0]); + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + + ret = ioctl(fd, TIOCSETD, &ldisc); + if (ret < 0) { + perror("ioctl(TIOCSETD)"); + exit(EXIT_FAILURE); + } + + pause(); + + return 0; +} diff --git a/Documentation/pps/ppstest.c b/Documentation/pps/ppstest.c new file mode 100644 index 0000000..d911bf6 --- /dev/null +++ b/Documentation/pps/ppstest.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include + +#include + +int find_source(char *path, pps_handle_t *handle, int *avail_mode) +{ + pps_params_t params; + int ret; + + printf("trying PPS source \"%s\"\n", path); + + /* Try to find the source by using the supplied "path" name */ + ret = open(path, O_RDWR); + if (ret < 0) { + fprintf(stderr, "unable to open device \"%s\" (%m)\n", path); + return ret; + } + + /* Open the PPS source (and check the file descriptor) */ + ret = time_pps_create(ret, handle); + if (ret < 0) { + fprintf(stderr, "cannot create a PPS source from device " + "\"%s\" (%m)\n", path); + return -1; + } + printf("found PPS source \"%s\"\n", path); + + /* Find out what features are supported */ + ret = time_pps_getcap(*handle, avail_mode); + if (ret < 0) { + fprintf(stderr, "cannot get capabilities (%m)\n"); + return -1; + } + if ((*avail_mode & PPS_CAPTUREASSERT) == 0) { + fprintf(stderr, "cannot CAPTUREASSERT\n"); + return -1; + } + if ((*avail_mode & PPS_OFFSETASSERT) == 0) { + fprintf(stderr, "cannot OFFSETASSERT\n"); + return -1; + } + + /* Capture assert timestamps, and compensate for a 675 nsec + * propagation delay */ + ret = time_pps_getparams(*handle, ¶ms); + if (ret < 0) { + fprintf(stderr, "cannot get parameters (%m)\n"); + return -1; + } + params.assert_offset.tv_sec = 0; + params.assert_offset.tv_nsec = 675; + params.mode |= PPS_CAPTUREASSERT | PPS_OFFSETASSERT; + ret = time_pps_setparams(*handle, ¶ms); + if (ret < 0) { + fprintf(stderr, "cannot set parameters (%m)\n"); + return -1; + } + + return 0; +} + +int fetch_source(int i, pps_handle_t *handle, int *avail_mode) +{ + struct timespec timeout; + pps_info_t infobuf; + int ret; + + /* create a zero-valued timeout */ + timeout.tv_sec = 3; + timeout.tv_nsec = 0; + +retry: + if (*avail_mode & PPS_CANWAIT) /* waits for the next event */ + ret = time_pps_fetch(*handle, PPS_TSFMT_TSPEC, &infobuf, + &timeout); + else { + sleep(1); + ret = time_pps_fetch(*handle, PPS_TSFMT_TSPEC, &infobuf, + &timeout); + } + if (ret < 0) { + if (ret == -EINTR) { + fprintf(stderr, "time_pps_fetch() got a signal!\n"); + goto retry; + } + + fprintf(stderr, "time_pps_fetch() error %d (%m)\n", ret); + return -1; + } + + printf("source %d - " + "assert %ld.%09ld, sequence: %ld - " + "clear %ld.%09ld, sequence: %ld\n", + i, + infobuf.assert_timestamp.tv_sec, + infobuf.assert_timestamp.tv_nsec, + infobuf.assert_sequence, + infobuf.clear_timestamp.tv_sec, + infobuf.clear_timestamp.tv_nsec, infobuf.clear_sequence); + + return 0; +} + +void usage(char *name) +{ + fprintf(stderr, "usage: %s [ ...]\n", name); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) +{ + int num; + pps_handle_t handle[4]; + int avail_mode[4]; + int i = 0; + int ret; + + /* Check the command line */ + if (argc < 2) + usage(argv[0]); + + for (i = 1; i < argc && i <= 4; i++) { + ret = find_source(argv[i], &handle[i - 1], &avail_mode[i - 1]); + if (ret < 0) + exit(EXIT_FAILURE); + } + + num = i - 1; + printf("ok, found %d source(s), now start fetching data...\n", num); + + /* loop, printing the most recent timestamp every second or so */ + while (1) { + for (i = 0; i < num; i++) { + ret = fetch_source(i, &handle[i], &avail_mode[i]); + if (ret < 0 && errno != ETIMEDOUT) + exit(EXIT_FAILURE); + } + } + + for (; i >= 0; i--) + time_pps_destroy(handle[i]); + + return 0; +} diff --git a/Documentation/pps/timepps.h b/Documentation/pps/timepps.h new file mode 100644 index 0000000..d2628d2 --- /dev/null +++ b/Documentation/pps/timepps.h @@ -0,0 +1,198 @@ +/* + * timepps.h -- PPS API main header + * + * Copyright (C) 2005-2007 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _SYS_TIMEPPS_H_ +#define _SYS_TIMEPPS_H_ + +#include +#include +#include +#include +#include + +#define LINUXPPS 1 /* signal we are using LinuxPPS */ + +/* + * New data structures + */ + +struct ntp_fp { + unsigned int integral; + unsigned int fractional; +}; + +union pps_timeu { + struct timespec tspec; + struct ntp_fp ntpfp; + unsigned long longpad[3]; +}; + +struct pps_info { + unsigned long assert_sequence; /* seq. num. of assert event */ + unsigned long clear_sequence; /* seq. num. of clear event */ + union pps_timeu assert_tu; /* time of assert event */ + union pps_timeu clear_tu; /* time of clear event */ + int current_mode; /* current mode bits */ +}; + +struct pps_params { + int api_version; /* API version # */ + int mode; /* mode bits */ + union pps_timeu assert_off_tu; /* offset compensation for assert */ + union pps_timeu clear_off_tu; /* offset compensation for clear */ +}; + +typedef int pps_handle_t; /* represents a PPS source */ +typedef unsigned long pps_seq_t; /* sequence number */ +typedef struct ntp_fp ntp_fp_t; /* NTP-compatible time stamp */ +typedef union pps_timeu pps_timeu_t; /* generic data type for time stamps */ +typedef struct pps_info pps_info_t; +typedef struct pps_params pps_params_t; + +#define assert_timestamp assert_tu.tspec +#define clear_timestamp clear_tu.tspec + +#define assert_timestamp_ntpfp assert_tu.ntpfp +#define clear_timestamp_ntpfp clear_tu.ntpfp + +#define assert_offset assert_off_tu.tspec +#define clear_offset clear_off_tu.tspec + +#define assert_offset_ntpfp assert_off_tu.ntpfp +#define clear_offset_ntpfp clear_off_tu.ntpfp + +/* + * The PPS API + */ + +static __inline int time_pps_create(int source, pps_handle_t *handle) +{ + int ret; + struct pps_kparams dummy; + + if (!handle) { + errno = EINVAL; + return -1; + } + + /* First we check if current device is a valid PPS one by + * doing a dummy PPS_GETPARAMS... + */ + ret = ioctl(source, PPS_GETPARAMS, &dummy); + if (ret) { + errno = EOPNOTSUPP; + return -1; + } + + /* ... then since in LinuxPPS there are no differences between a + * "PPS source" and a "PPS handle", we simply return the same value. + */ + *handle = source; + + return 0; +} + +static __inline int time_pps_destroy(pps_handle_t handle) +{ + return close(handle); +} + +static __inline int time_pps_getparams(pps_handle_t handle, + pps_params_t *ppsparams) +{ + int ret; + struct pps_kparams __ppsparams; + + ret = ioctl(handle, PPS_GETPARAMS, &__ppsparams); + + ppsparams->api_version = __ppsparams.api_version; + ppsparams->mode = __ppsparams.mode; + ppsparams->assert_off_tu.tspec.tv_sec = __ppsparams.assert_off_tu.sec; + ppsparams->assert_off_tu.tspec.tv_nsec = __ppsparams.assert_off_tu.nsec; + ppsparams->clear_off_tu.tspec.tv_sec = __ppsparams.clear_off_tu.sec; + ppsparams->clear_off_tu.tspec.tv_nsec = __ppsparams.clear_off_tu.nsec; + + return ret; +} + +static __inline int time_pps_setparams(pps_handle_t handle, + const pps_params_t *ppsparams) +{ + struct pps_kparams __ppsparams; + + __ppsparams.api_version = ppsparams->api_version; + __ppsparams.mode = ppsparams->mode; + __ppsparams.assert_off_tu.sec = ppsparams->assert_off_tu.tspec.tv_sec; + __ppsparams.assert_off_tu.nsec = ppsparams->assert_off_tu.tspec.tv_nsec; + __ppsparams.clear_off_tu.sec = ppsparams->clear_off_tu.tspec.tv_sec; + __ppsparams.clear_off_tu.nsec = ppsparams->clear_off_tu.tspec.tv_nsec; + + return ioctl(handle, PPS_SETPARAMS, &__ppsparams); +} + +/* Get capabilities for handle */ +static __inline int time_pps_getcap(pps_handle_t handle, int *mode) +{ + return ioctl(handle, PPS_GETCAP, mode); +} + +static __inline int time_pps_fetch(pps_handle_t handle, const int tsformat, + pps_info_t *ppsinfobuf, + const struct timespec *timeout) +{ + struct pps_fdata __fdata; + int ret; + + /* Sanity checks */ + if (tsformat != PPS_TSFMT_TSPEC) { + errno = EINVAL; + return -1; + } + + if (timeout) { + __fdata.timeout.sec = timeout->tv_sec; + __fdata.timeout.nsec = timeout->tv_nsec; + __fdata.timeout.flags = ~PPS_TIME_INVALID; + } else + __fdata.timeout.flags = PPS_TIME_INVALID; + + ret = ioctl(handle, PPS_FETCH, &__fdata); + + ppsinfobuf->assert_sequence = __fdata.info.assert_sequence; + ppsinfobuf->clear_sequence = __fdata.info.clear_sequence; + ppsinfobuf->assert_tu.tspec.tv_sec = __fdata.info.assert_tu.sec; + ppsinfobuf->assert_tu.tspec.tv_nsec = __fdata.info.assert_tu.nsec; + ppsinfobuf->clear_tu.tspec.tv_sec = __fdata.info.clear_tu.sec; + ppsinfobuf->clear_tu.tspec.tv_nsec = __fdata.info.clear_tu.nsec; + ppsinfobuf->current_mode = __fdata.info.current_mode; + + return ret; +} + +static __inline int time_pps_kcbind(pps_handle_t handle, + const int kernel_consumer, + const int edge, const int tsformat) +{ + /* LinuxPPS doesn't implement kernel consumer feature */ + errno = EOPNOTSUPP; + return -1; +} + +#endif /* _SYS_TIMEPPS_H_ */ diff --git a/Documentation/serial/tty.txt b/Documentation/serial/tty.txt index 8e65c44..3fc812a 100644 --- a/Documentation/serial/tty.txt +++ b/Documentation/serial/tty.txt @@ -100,6 +100,10 @@ write_wakeup() - May be called at any point between open and close. is permitted to call the driver write method from this function. In such a situation defer it. +dcd_change() - Report to the tty line the current DCD pin status + changes and the relative timestamp. The timestamp + can be NULL. + Driver Access diff --git a/MAINTAINERS b/MAINTAINERS index 618c1ef..01d6667 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3421,6 +3421,13 @@ P: James Chapman M: jchapman@katalix.com S: Maintained +PPS SUPPORT +P: Rodolfo Giometti +M: giometti@enneenne.com +W: http://wiki.enneenne.com/index.php/LinuxPPS_support +L: linuxpps@ml.enneenne.com (subscribers-only) +S: Maintained + PREEMPTIBLE KERNEL P: Robert Love M: rml@tech9.net diff --git a/arch/x86/kernel/irq_32.c b/arch/x86/kernel/irq_32.c index a513826..7fce1e2 100644 --- a/arch/x86/kernel/irq_32.c +++ b/arch/x86/kernel/irq_32.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,11 @@ EXPORT_PER_CPU_SYMBOL(irq_stat); DEFINE_PER_CPU(struct pt_regs *, irq_regs); EXPORT_PER_CPU_SYMBOL(irq_regs); +#ifdef CONFIG_PPS_IRQ_EVENTS +struct timespec pps_irq_ts[NR_IRQS]; +EXPORT_SYMBOL(pps_irq_ts); +#endif + #ifdef CONFIG_DEBUG_STACKOVERFLOW /* Debugging check for stack overflow: is there less than 1KB free? */ static int check_stack_overflow(void) @@ -204,7 +210,12 @@ unsigned int do_IRQ(struct pt_regs *regs) unsigned vector = ~regs->orig_ax; struct irq_desc *desc; unsigned irq; +#ifdef CONFIG_PPS_IRQ_EVENTS + struct timespec ts; + /* Get IRQ timestamps as soon as possible for the PPS layer */ + getnstimeofday(&ts); +#endif old_regs = set_irq_regs(regs); irq_enter(); @@ -219,6 +230,11 @@ unsigned int do_IRQ(struct pt_regs *regs) BUG(); } +#ifdef CONFIG_PPS_IRQ_EVENTS + /* Then, after sanity check, store the IRQ timestamp */ + pps_irq_ts[irq] = ts; +#endif + if (!execute_on_irq_stack(overflow, desc, irq)) { if (unlikely(overflow)) print_stack_overflow(); diff --git a/arch/x86/kernel/irq_64.c b/arch/x86/kernel/irq_64.c index 60eb84e..a1457a0 100644 --- a/arch/x86/kernel/irq_64.c +++ b/arch/x86/kernel/irq_64.c @@ -17,6 +17,12 @@ #include #include #include +#include + +#ifdef CONFIG_PPS_IRQ_EVENTS +struct timespec pps_irq_ts[NR_IRQS]; +EXPORT_SYMBOL(pps_irq_ts); +#endif #ifdef CONFIG_DEBUG_STACKOVERFLOW /* @@ -51,6 +57,12 @@ asmlinkage unsigned int do_IRQ(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); struct irq_desc *desc; +#ifdef CONFIG_PPS_IRQ_EVENTS + struct timespec ts; + + /* Get IRQ timestamps as soon as possible for the PPS layer */ + getnstimeofday(&ts); +#endif /* high bit used in ret_from_ code */ unsigned vector = ~regs->orig_ax; @@ -65,9 +77,14 @@ asmlinkage unsigned int do_IRQ(struct pt_regs *regs) #endif desc = irq_to_desc(irq); - if (likely(desc)) + if (likely(desc)) { +#ifdef CONFIG_PPS_IRQ_EVENTS + /* Then, after sanity check, store the IRQ timestamp */ + pps_irq_ts[irq] = ts; +#endif + generic_handle_irq_desc(irq, desc); - else { + } else { if (!disable_apic) ack_APIC_irq(); diff --git a/drivers/Kconfig b/drivers/Kconfig index 2f557f5..4b679d2 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig" source "drivers/spi/Kconfig" +source "drivers/pps/Kconfig" + source "drivers/gpio/Kconfig" source "drivers/w1/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index fceb71a..da6329a 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_INPUT) += input/ obj-$(CONFIG_I2O) += message/ obj-$(CONFIG_RTC_LIB) += rtc/ obj-y += i2c/ +obj-$(CONFIG_PPS) += pps/ obj-$(CONFIG_W1) += w1/ obj-$(CONFIG_POWER_SUPPLY) += power/ obj-$(CONFIG_HWMON) += hwmon/ diff --git a/drivers/char/lp.c b/drivers/char/lp.c index e444c2d..dca8a39 100644 --- a/drivers/char/lp.c +++ b/drivers/char/lp.c @@ -760,6 +760,27 @@ static struct console lpcons = { #endif /* console on line printer */ +/* Support for PPS signal on the line printer */ + +#ifdef CONFIG_PPS_CLIENT_LP + +static void lp_pps_echo(int source, int event, void *data) +{ + struct parport *port = data; + unsigned char status = parport_read_status(port); + + /* echo event via SEL bit */ + parport_write_control(port, + parport_read_control(port) | PARPORT_CONTROL_SELECT); + + /* signal no event */ + if ((status & PARPORT_STATUS_ACK) != 0) + parport_write_control(port, + parport_read_control(port) & ~PARPORT_CONTROL_SELECT); +} + +#endif + /* --- initialisation code ------------------------------------- */ static int parport_nr[LP_NO] = { [0 ... LP_NO-1] = LP_PARPORT_UNSPEC }; @@ -831,6 +852,38 @@ static int lp_register(int nr, struct parport *port) } #endif +#ifdef CONFIG_PPS_CLIENT_LP + port->pps_info.owner = THIS_MODULE; + port->pps_info.dev = port->dev; + snprintf(port->pps_info.path, PPS_MAX_NAME_LEN, "/dev/lp%d", nr); + + /* No PPS support if lp port has no IRQ line */ + if (port->irq != PARPORT_IRQ_NONE) { + strncpy(port->pps_info.name, port->name, PPS_MAX_NAME_LEN); + + port->pps_info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | \ + PPS_ECHOASSERT | \ + PPS_CANWAIT | PPS_TSFMT_TSPEC; + + port->pps_info.echo = lp_pps_echo; + + port->pps_source = pps_register_source(&(port->pps_info), + PPS_CAPTUREASSERT | PPS_OFFSETASSERT); + if (port->pps_source < 0) + dev_err(port->dev, + "cannot register PPS source \"%s\"\n", + port->pps_info.path); + else + dev_info(port->dev, "PPS source #%d \"%s\" added\n", + port->pps_source, port->pps_info.path); + } else { + port->pps_source = -1; + dev_err(port->dev, "PPS support disabled because port \"%s\" " + "is in polling mode\n", + port->pps_info.path); + } +#endif + return 0; } @@ -873,6 +926,14 @@ static void lp_detach (struct parport *port) console_registered = NULL; } #endif /* CONFIG_LP_CONSOLE */ + +#ifdef CONFIG_PPS_CLIENT_LP + if (port->pps_source >= 0) { + pps_unregister_source(port->pps_source); + dev_dbg(port->dev, "PPS source #%d \"%s\" removed\n", + port->pps_source, port->pps_info.path); + } +#endif } static struct parport_driver lp_driver = { diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c index efbfe96..9db2e35 100644 --- a/drivers/char/n_tty.c +++ b/drivers/char/n_tty.c @@ -43,10 +43,10 @@ #include #include #include -#include #include #include #include +#include #include #include @@ -194,7 +194,7 @@ static void reset_buffer_flags(struct tty_struct *tty) * Locking: ctrl_lock, read_lock. */ -static void n_tty_flush_buffer(struct tty_struct *tty) +void n_tty_flush_buffer(struct tty_struct *tty) { unsigned long flags; /* clear everything and unthrottle the driver */ @@ -210,6 +210,7 @@ static void n_tty_flush_buffer(struct tty_struct *tty) } spin_unlock_irqrestore(&tty->ctrl_lock, flags); } +EXPORT_SYMBOL_GPL(n_tty_flush_buffer); /** * n_tty_chars_in_buffer - report available bytes @@ -221,7 +222,7 @@ static void n_tty_flush_buffer(struct tty_struct *tty) * Locking: read_lock */ -static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty) +ssize_t n_tty_chars_in_buffer(struct tty_struct *tty) { unsigned long flags; ssize_t n = 0; @@ -237,6 +238,7 @@ static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty) spin_unlock_irqrestore(&tty->read_lock, flags); return n; } +EXPORT_SYMBOL_GPL(n_tty_chars_in_buffer); /** * is_utf8_continuation - utf8 multibyte check @@ -921,13 +923,14 @@ handle_newline: * IO must be woken up */ -static void n_tty_write_wakeup(struct tty_struct *tty) +void n_tty_write_wakeup(struct tty_struct *tty) { if (tty->fasync) { set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); kill_fasync(&tty->fasync, SIGIO, POLL_OUT); } } +EXPORT_SYMBOL_GPL(n_tty_write_wakeup); /** * n_tty_receive_buf - data receive @@ -942,7 +945,7 @@ static void n_tty_write_wakeup(struct tty_struct *tty) * calls one at a time and in order (or using flush_to_ldisc) */ -static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, +void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { const unsigned char *p; @@ -1016,6 +1019,7 @@ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, if (tty->receive_room < TTY_THRESHOLD_THROTTLE) tty_throttle(tty); } +EXPORT_SYMBOL_GPL(n_tty_receive_buf); int is_ignored(int sig) { @@ -1037,7 +1041,7 @@ int is_ignored(int sig) * Locking: Caller holds tty->termios_mutex */ -static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) +void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) { int canon_change = 1; BUG_ON(!tty); @@ -1116,6 +1120,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) wake_up_interruptible(&tty->write_wait); wake_up_interruptible(&tty->read_wait); } +EXPORT_SYMBOL_GPL(n_tty_set_termios); /** * n_tty_close - close the ldisc for this tty @@ -1127,7 +1132,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) * ldisc methods are in progress. */ -static void n_tty_close(struct tty_struct *tty) +void n_tty_close(struct tty_struct *tty) { n_tty_flush_buffer(tty); if (tty->read_buf) { @@ -1135,6 +1140,7 @@ static void n_tty_close(struct tty_struct *tty) tty->read_buf = NULL; } } +EXPORT_SYMBOL_GPL(n_tty_close); /** * n_tty_open - open an ldisc @@ -1146,7 +1152,7 @@ static void n_tty_close(struct tty_struct *tty) * until a close. */ -static int n_tty_open(struct tty_struct *tty) +int n_tty_open(struct tty_struct *tty) { if (!tty) return -EINVAL; @@ -1165,6 +1171,7 @@ static int n_tty_open(struct tty_struct *tty) tty->closing = 0; return 0; } +EXPORT_SYMBOL_GPL(n_tty_open); static inline int input_available_p(struct tty_struct *tty, int amt) { @@ -1279,7 +1286,7 @@ static int job_control(struct tty_struct *tty, struct file *file) * This code must be sure never to sleep through a hangup. */ -static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, +ssize_t n_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr) { unsigned char __user *b = buf; @@ -1479,6 +1486,7 @@ do_it_again: n_tty_set_room(tty); return retval; } +EXPORT_SYMBOL_GPL(n_tty_read); /** * n_tty_write - write function for tty @@ -1495,7 +1503,7 @@ do_it_again: * This code must be sure never to sleep through a hangup. */ -static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, +ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) { const unsigned char *b = buf; @@ -1567,6 +1575,7 @@ break_out: remove_wait_queue(&tty->write_wait, &wait); return (b - buf) ? b - buf : retval; } +EXPORT_SYMBOL_GPL(n_tty_write); /** * n_tty_poll - poll method for N_TTY @@ -1582,7 +1591,7 @@ break_out: * Called without the kernel lock held - fine */ -static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, +unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, poll_table *wait) { unsigned int mask = 0; @@ -1609,6 +1618,7 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, mask |= POLLOUT | POLLWRNORM; return mask; } +EXPORT_SYMBOL_GPL(n_tty_poll); static unsigned long inq_canon(struct tty_struct *tty) { diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig new file mode 100644 index 0000000..7c0b094 --- /dev/null +++ b/drivers/pps/Kconfig @@ -0,0 +1,47 @@ +# +# PPS support configuration +# + +menu "PPS support" + +config PPS + tristate "PPS support" + depends on EXPERIMENTAL + ---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. + +config PPS_IRQ_EVENTS + bool "Use low level IRQ timestamps" + depends on PPS && (X86_32 || X86_64) + default yes + help + Say Y here if you wish using low level IRQ timestamps to register + PPS events. + + This should improve PPS resolution but it delays echo functions + call. Note also that this function is not implemented on all + platforms and PPS clients! + +config PPS_DEBUG + bool "PPS debugging messages" + depends on PPS + 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. + +source drivers/pps/clients/Kconfig + +endmenu diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile new file mode 100644 index 0000000..98960dd --- /dev/null +++ b/drivers/pps/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the PPS core. +# + +pps_core-y := pps.o kapi.o sysfs.o +obj-$(CONFIG_PPS) := pps_core.o +obj-y += clients/ + +ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/clients/Kconfig b/drivers/pps/clients/Kconfig new file mode 100644 index 0000000..b4054cd --- /dev/null +++ b/drivers/pps/clients/Kconfig @@ -0,0 +1,35 @@ +# +# PPS clients configuration +# + +if PPS + +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 ktimer.ko. + +config PPS_CLIENT_LDISC + tristate "PPS line discipline" + depends on PPS + help + If you say yes here you get support for a PPS source connected + with the CD (Carrier Detect) pin of your serial port. + +comment "Parallel printer support (forced off)" + depends on !( PRINTER != n && !(PPS = m && PRINTER = y)) + +config PPS_CLIENT_LP + bool "Parallel printer support" + depends on PRINTER != n && !(PPS = m && PRINTER = y) + help + If you say yes here you get support for a PPS source connected + with the interrupt pin of your parallel port. + +endif diff --git a/drivers/pps/clients/Makefile b/drivers/pps/clients/Makefile new file mode 100644 index 0000000..9f5b988 --- /dev/null +++ b/drivers/pps/clients/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for PPS clients. +# + +obj-$(CONFIG_PPS_CLIENT_KTIMER) += ktimer.o +obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o + +ifeq ($(CONFIG_PPS_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/pps/clients/ktimer.c b/drivers/pps/clients/ktimer.c new file mode 100644 index 0000000..259baa7 --- /dev/null +++ b/drivers/pps/clients/ktimer.c @@ -0,0 +1,124 @@ +/* + * ktimer.c -- kernel timer test client + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include + +#include + +/* + * Global variables + */ + +static int source; +static struct timer_list ktimer; + +/* + * The kernel timer + */ + +static void pps_ktimer_event(unsigned long ptr) +{ + struct timespec __ts; + struct pps_ktime ts; + + /* First of all we get the time stamp... */ + getnstimeofday(&__ts); + + pr_info("PPS event at %lu\n", jiffies); + + /* ... and translate it to PPS time data struct */ + ts.sec = __ts.tv_sec; + ts.nsec = __ts.tv_nsec; + + pps_event(source, &ts, PPS_CAPTUREASSERT, NULL); + + mod_timer(&ktimer, jiffies + HZ); +} + +/* + * The echo function + */ + +static void pps_ktimer_echo(int source, int event, void *data) +{ + pr_info("echo %s %s for source %d\n", + event & PPS_CAPTUREASSERT ? "assert" : "", + event & PPS_CAPTURECLEAR ? "clear" : "", + source); +} + +/* + * 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, + .echo = pps_ktimer_echo, + .owner = THIS_MODULE, +}; + +/* + * Module staff + */ + +static void __exit pps_ktimer_exit(void) +{ + del_timer_sync(&ktimer); + pps_unregister_source(source); + + pr_info("ktimer PPS source unregistered\n"); +} + +static int __init pps_ktimer_init(void) +{ + int ret; + + ret = pps_register_source(&pps_ktimer_info, + PPS_CAPTUREASSERT | PPS_OFFSETASSERT); + if (ret < 0) { + printk(KERN_ERR "cannot register ktimer source\n"); + return ret; + } + source = ret; + + setup_timer(&ktimer, pps_ktimer_event, 0); + mod_timer(&ktimer, jiffies + HZ); + + pr_info("ktimer PPS source registered at %d\n", source); + + 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 0000000..f16396b --- /dev/null +++ b/drivers/pps/clients/pps-ldisc.c @@ -0,0 +1,155 @@ +/* + * pps-ldisc.c -- PPS line discipline + * + * + * Copyright (C) 2008 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#define PPS_TTY_MAGIC 0x0001 + +static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status, + struct timespec *ts) +{ + long id = (long) tty->disc_data; + struct timespec __ts; + struct pps_ktime pps_ts; + + /* First of all we get the time stamp... */ + getnstimeofday(&__ts); + + /* Does caller give us a timestamp? */ + if (ts) { /* Yes. Let's use it! */ + pps_ts.sec = ts->tv_sec; + pps_ts.nsec = ts->tv_nsec; + } else { /* No. Do it ourself! */ + pps_ts.sec = __ts.tv_sec; + pps_ts.nsec = __ts.tv_nsec; + } + + /* Now do the PPS event report */ + pps_event(id, &pps_ts, status ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR, + NULL); + + pr_debug("PPS %s at %lu on source #%d\n", + status ? "assert" : "clear", jiffies, (int) id); +} + +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; + long 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; + + ret = pps_register_source(&info, PPS_CAPTUREBOTH | \ + PPS_OFFSETASSERT | PPS_OFFSETCLEAR); + if (ret < 0) { + pr_err("cannot register PPS source \"%s\"\n", info.path); + return ret; + } + tty->disc_data = (void *) ret; + + /* Should open N_TTY ldisc too */ + ret = n_tty_open(tty); + if (ret < 0) + pps_unregister_source((long) tty->disc_data); + + pr_info("PPS source #%d \"%s\" added\n", (int) ret, info.path); + + return 0; +} + +static void pps_tty_close(struct tty_struct *tty) +{ + long id = (long) tty->disc_data; + + pps_unregister_source(id); + n_tty_close(tty); + + pr_info("PPS source #%d removed\n", (int) id); +} + +struct tty_ldisc_ops pps_ldisc_ops = { + .owner = THIS_MODULE, + .magic = PPS_TTY_MAGIC, + .name = "pps_tty", + .dcd_change = pps_tty_dcd_change, + .open = pps_tty_open, + .close = pps_tty_close, + + /* Now we should use N_TTY ldisc methods in order to have + * normal tty behaviour + */ + .flush_buffer = n_tty_flush_buffer, + .chars_in_buffer = n_tty_chars_in_buffer, + .read = n_tty_read, + .write = n_tty_write, + .ioctl = n_tty_ioctl_helper, + .set_termios = n_tty_set_termios, + .poll = n_tty_poll, + .receive_buf = n_tty_receive_buf, + .write_wakeup = n_tty_write_wakeup +}; + +/* + * Module stuff + */ + +static int __init pps_tty_init(void) +{ + int err; + + err = tty_register_ldisc(N_PPS, &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) +{ + int err; + + err = tty_unregister_ldisc(N_PPS); + if (err) + pr_err("can't unregister PPS line discipline\n"); + else + pr_info("PPS line discipline removed\n"); +} + +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/kapi.c b/drivers/pps/kapi.c new file mode 100644 index 0000000..e0b75ed --- /dev/null +++ b/drivers/pps/kapi.c @@ -0,0 +1,329 @@ +/* + * kernel API + * + * + * Copyright (C) 2005-2009 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Global variables + */ + +DEFINE_SPINLOCK(pps_idr_lock); +DEFINE_IDR(pps_idr); + +/* + * 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; +} + +/* + * Exported functions + */ + +/* pps_get_source - find a PPS source + * @source: the PPS source ID. + * + * This function is used to find an already registered PPS source into the + * system. + * + * The function returns NULL if found nothing, otherwise it returns a pointer + * to the PPS source data struct (the refcounter is incremented by 1). + */ + +struct pps_device *pps_get_source(int source) +{ + struct pps_device *pps; + unsigned long flags; + + spin_lock_irqsave(&pps_idr_lock, flags); + + pps = idr_find(&pps_idr, source); + if (pps != NULL) + atomic_inc(&pps->usage); + + spin_unlock_irqrestore(&pps_idr_lock, flags); + + return pps; +} + +/* pps_put_source - free the PPS source data + * @pps: a pointer to the PPS source. + * + * This function is used to free a PPS data struct if its refcount is 0. + */ + +void pps_put_source(struct pps_device *pps) +{ + unsigned long flags; + + spin_lock_irqsave(&pps_idr_lock, flags); + BUG_ON(atomic_read(&pps->usage) == 0); + + if (!atomic_dec_and_test(&pps->usage)) { + pps = NULL; + goto exit; + } + + /* No more reference to the PPS source. We can safely remove the + * PPS data struct. + */ + idr_remove(&pps_idr, pps->id); + +exit: + spin_unlock_irqrestore(&pps_idr_lock, flags); + kfree(pps); +} + +/* 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 source ID. + */ + +int pps_register_source(struct pps_source_info *info, int default_params) +{ + struct pps_device *pps; + int id; + int err; + + /* Sanity checks */ + if ((info->mode & default_params) != default_params) { + printk(KERN_ERR "pps: %s: unsupported default parameters\n", + info->name); + err = -EINVAL; + goto pps_register_source_exit; + } + if ((info->mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) != 0 && + info->echo == NULL) { + printk(KERN_ERR "pps: %s: echo function is not defined\n", + info->name); + err = -EINVAL; + goto pps_register_source_exit; + } + if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { + printk(KERN_ERR "pps: %s: unspecified time format\n", + info->name); + err = -EINVAL; + goto pps_register_source_exit; + } + + /* Allocate memory for the new PPS source struct */ + pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL); + if (pps == NULL) { + err = -ENOMEM; + goto pps_register_source_exit; + } + + /* These initializations must be done before calling idr_get_new() + * in order to avoid reces into pps_event(). + */ + pps->params.api_version = PPS_API_VERS; + pps->params.mode = default_params; + pps->info = *info; + + init_waitqueue_head(&pps->queue); + spin_lock_init(&pps->lock); + atomic_set(&pps->usage, 1); + + /* Get new ID for the new PPS source */ + if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) { + err = -ENOMEM; + goto kfree_pps; + } + + spin_lock_irq(&pps_idr_lock); + + /* Now really allocate the PPS source. + * After idr_get_new() calling the new source will be freely available + * into the kernel. + */ + err = idr_get_new(&pps_idr, pps, &id); + if (err < 0) { + spin_unlock_irq(&pps_idr_lock); + goto kfree_pps; + } + + id = id & MAX_ID_MASK; + if (id >= PPS_MAX_SOURCES) { + spin_unlock_irq(&pps_idr_lock); + + printk(KERN_ERR "pps: %s: too many PPS sources in the system\n", + info->name); + err = -EBUSY; + goto free_idr; + } + pps->id = id; + + spin_unlock_irq(&pps_idr_lock); + + /* Create the char device */ + err = pps_register_cdev(pps); + if (err < 0) { + printk(KERN_ERR "pps: %s: unable to create char device\n", + info->name); + goto free_idr; + } + + pr_info("new PPS source %s at ID %d\n", info->name, id); + + return id; + +free_idr: + spin_lock_irq(&pps_idr_lock); + idr_remove(&pps_idr, id); + spin_unlock_irq(&pps_idr_lock); + +kfree_pps: + kfree(pps); + +pps_register_source_exit: + printk(KERN_ERR "pps: %s: unable to register source\n", info->name); + + return err; +} +EXPORT_SYMBOL(pps_register_source); + +/* pps_unregister_source - remove a PPS source from the system + * @source: the PPS source ID + * + * This function is used to remove a previously registered PPS source from + * the system. + */ + +void pps_unregister_source(int source) +{ + struct pps_device *pps; + + spin_lock_irq(&pps_idr_lock); + pps = idr_find(&pps_idr, source); + + if (!pps) { + BUG(); + spin_unlock_irq(&pps_idr_lock); + return; + } + spin_unlock_irq(&pps_idr_lock); + + pps_unregister_cdev(pps); + pps_put_source(pps); +} +EXPORT_SYMBOL(pps_unregister_source); + +/* pps_event - register a PPS event into the system + * @source: the PPS source ID + * @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 source it will be called + * as: + * pps->info.echo(source, event, data); + */ + +void pps_event(int source, struct pps_ktime *ts, int event, void *data) +{ + struct pps_device *pps; + unsigned long flags; + + if ((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0) { + printk(KERN_ERR "pps: unknown event (%x) for source %d\n", + event, source); + return; + } + + pps = pps_get_source(source); + if (!pps) + return; + + pr_debug("PPS event on source %d at %llu.%06u\n", + pps->id, (unsigned long long) ts->sec, ts->nsec); + + spin_lock_irqsave(&pps->lock, flags); + + /* Must call the echo function? */ + if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR))) + pps->info.echo(source, event, data); + + /* Check the event */ + pps->current_mode = pps->params.mode; + if (event & PPS_CAPTUREASSERT) { + /* We have to add an offset? */ + if (pps->params.mode & PPS_OFFSETASSERT) + pps_add_offset(ts, &pps->params.assert_off_tu); + + /* Save the time stamp */ + pps->assert_tu = *ts; + pps->assert_sequence++; + pr_debug("capture assert seq #%u for source %d\n", + pps->assert_sequence, source); + } + if (event & PPS_CAPTURECLEAR) { + /* We have to add an offset? */ + if (pps->params.mode & PPS_OFFSETCLEAR) + pps_add_offset(ts, &pps->params.clear_off_tu); + + /* Save the time stamp */ + pps->clear_tu = *ts; + pps->clear_sequence++; + pr_debug("capture clear seq #%u for source %d\n", + pps->clear_sequence, source); + } + + pps->go = ~0; + wake_up_interruptible(&pps->queue); + + kill_fasync(&pps->async_queue, SIGIO, POLL_IN); + + spin_unlock_irqrestore(&pps->lock, flags); + + /* Now we can release the PPS source for (possible) deregistration */ + pps_put_source(pps); +} +EXPORT_SYMBOL(pps_event); diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c new file mode 100644 index 0000000..23ea070 --- /dev/null +++ b/drivers/pps/pps.c @@ -0,0 +1,312 @@ +/* + * PPS core file + * + * + * Copyright (C) 2005-2009 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Local variables + */ + +static dev_t pps_devt; +static struct class *pps_class; + +/* + * Char device methods + */ + +static unsigned int pps_cdev_poll(struct file *file, poll_table *wait) +{ + struct pps_device *pps = file->private_data; + + poll_wait(file, &pps->queue, wait); + + return POLLIN | POLLRDNORM; +} + +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 long pps_cdev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct pps_device *pps = file->private_data; + struct pps_kparams params; + struct pps_fdata fdata; + unsigned long ticks; + void __user *uarg = (void __user *) arg; + int __user *iuarg = (int __user *) arg; + int err; + + switch (cmd) { + case PPS_GETPARAMS: + pr_debug("PPS_GETPARAMS: source %d\n", pps->id); + + /* Return current parameters */ + err = copy_to_user(uarg, &pps->params, + sizeof(struct pps_kparams)); + if (err) + return -EFAULT; + + break; + + case PPS_SETPARAMS: + pr_debug("PPS_SETPARAMS: source %d\n", pps->id); + + /* 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))) { + pr_debug("capture mode unspecified (%x)\n", + params.mode); + return -EINVAL; + } + + /* Check for supported capabilities */ + if ((params.mode & ~pps->info.mode) != 0) { + pr_debug("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 */ + pr_debug("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; + + spin_unlock_irq(&pps->lock); + + break; + + case PPS_GETCAP: + pr_debug("PPS_GETCAP: source %d\n", pps->id); + + err = put_user(pps->info.mode, iuarg); + if (err) + return -EFAULT; + + break; + + case PPS_FETCH: + pr_debug("PPS_FETCH: source %d\n", pps->id); + + err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata)); + if (err) + return -EFAULT; + + pps->go = 0; + + /* Manage the timeout */ + if (fdata.timeout.flags & PPS_TIME_INVALID) + err = wait_event_interruptible(pps->queue, pps->go); + else { + pr_debug("timeout %lld.%09d\n", + (long long) fdata.timeout.sec, + fdata.timeout.nsec); + ticks = fdata.timeout.sec * HZ; + ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ); + + if (ticks != 0) { + err = wait_event_interruptible_timeout( + pps->queue, pps->go, ticks); + if (err == 0) + return -ETIMEDOUT; + } + } + + /* Check for pending signals */ + if (err == -ERESTARTSYS) { + pr_debug("pending signal caught\n"); + return -EINTR; + } + + /* Return the fetched timestamp */ + spin_lock_irq(&pps->lock); + + 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; + + default: + return -ENOTTY; + break; + } + + return 0; +} + +static int pps_cdev_open(struct inode *inode, struct file *file) +{ + struct pps_device *pps = container_of(inode->i_cdev, + struct pps_device, cdev); + int found; + + found = pps_get_source(pps->id) != 0; + if (!found) + 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; + + /* Free the PPS source and wake up (possible) deregistration */ + pps_put_source(pps); + + return 0; +} + +/* + * Char device stuff + */ + +static const struct file_operations pps_cdev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .poll = pps_cdev_poll, + .fasync = pps_cdev_fasync, + .unlocked_ioctl = pps_cdev_ioctl, + .open = pps_cdev_open, + .release = pps_cdev_release, +}; + +int pps_register_cdev(struct pps_device *pps) +{ + int err; + + pps->devno = MKDEV(MAJOR(pps_devt), pps->id); + cdev_init(&pps->cdev, &pps_cdev_fops); + pps->cdev.owner = pps->info.owner; + + err = cdev_add(&pps->cdev, pps->devno, 1); + if (err) { + printk(KERN_ERR "pps: %s: failed to add char device %d:%d\n", + pps->info.name, MAJOR(pps_devt), pps->id); + return err; + } + pps->dev = device_create(pps_class, pps->info.dev, pps->devno, NULL, + "pps%d", pps->id); + if (err) + goto del_cdev; + dev_set_drvdata(pps->dev, pps); + + pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, + MAJOR(pps_devt), pps->id); + + return 0; + +del_cdev: + cdev_del(&pps->cdev); + + return err; +} + +void pps_unregister_cdev(struct pps_device *pps) +{ + device_destroy(pps_class, pps->devno); + cdev_del(&pps->cdev); +} + +/* + * Module stuff + */ + +static void __exit pps_exit(void) +{ + class_destroy(pps_class); + unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES); +} + +static int __init pps_init(void) +{ + int err; + + pps_class = class_create(THIS_MODULE, "pps"); + if (!pps_class) { + printk(KERN_ERR "pps: failed to allocate class\n"); + return -ENOMEM; + } + pps_class->dev_attrs = pps_attrs; + + err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps"); + if (err < 0) { + printk(KERN_ERR "pps: 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_destroy(pps_class); + + return err; +} + +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 0000000..b4cc850 --- /dev/null +++ b/drivers/pps/sysfs.c @@ -0,0 +1,98 @@ +/* + * PPS sysfs support + * + * + * Copyright (C) 2007-2009 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include + +/* + * Attribute functions + */ + +static ssize_t pps_show_assert(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 ssize_t pps_show_clear(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 ssize_t pps_show_mode(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 ssize_t pps_show_echo(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 ssize_t pps_show_name(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 ssize_t pps_show_path(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); +} + +struct device_attribute pps_attrs[] = { + __ATTR(assert, S_IRUGO, pps_show_assert, NULL), + __ATTR(clear, S_IRUGO, pps_show_clear, NULL), + __ATTR(mode, S_IRUGO, pps_show_mode, NULL), + __ATTR(echo, S_IRUGO, pps_show_echo, NULL), + __ATTR(name, S_IRUGO, pps_show_name, NULL), + __ATTR(path, S_IRUGO, pps_show_path, NULL), + __ATTR_NULL, +}; diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index 303272a..b425116 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -2313,6 +2313,18 @@ serial8250_set_termios(struct uart_port *port, struct ktermios *termios, } static void +serial8250_set_ldisc(struct uart_port *port) +{ + int line = port->line; + + if (line >= port->info->port.tty->driver->num) + return; + + if (port->info->port.tty->ldisc.ops->num == N_PPS) + serial8250_enable_ms(port); +} + +static void serial8250_pm(struct uart_port *port, unsigned int state, unsigned int oldstate) { @@ -2523,6 +2535,7 @@ static struct uart_ops serial8250_pops = { .startup = serial8250_startup, .shutdown = serial8250_shutdown, .set_termios = serial8250_set_termios, + .set_ldisc = serial8250_set_ldisc, .pm = serial8250_pm, .type = serial8250_type, .release_port = serial8250_release_port, diff --git a/include/linux/Kbuild b/include/linux/Kbuild index e531783..7f850e7 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -309,6 +309,7 @@ unifdef-y += pmu.h unifdef-y += poll.h unifdef-y += ppp_defs.h unifdef-y += ppp-comp.h +unifdef-y += pps.h unifdef-y += ptrace.h unifdef-y += qnx4_fs.h unifdef-y += quota.h diff --git a/include/linux/parport.h b/include/linux/parport.h index e1f83c5..4276941 100644 --- a/include/linux/parport.h +++ b/include/linux/parport.h @@ -98,6 +98,7 @@ typedef enum { #include #include #include +#include #include #include #include @@ -326,6 +327,11 @@ struct parport { struct list_head full_list; struct parport *slaves[3]; + +#ifdef CONFIG_PPS_CLIENT_LP + struct pps_source_info pps_info; + int pps_source; /* PPS source ID */ +#endif }; #define DEFAULT_SPIN_TIME 500 /* us */ @@ -514,6 +520,22 @@ extern int parport_daisy_select (struct parport *port, int daisy, int mode); /* Lowlevel drivers _can_ call this support function to handle irqs. */ static inline void parport_generic_irq(struct parport *port) { +#ifdef CONFIG_PPS_CLIENT_LP + struct timespec __ts; + struct pps_ktime ts; + + /* First of all we get the time stamp... */ + getnstimeofday(&__ts); + + /* ... and translate it to PPS time data struct */ + ts.sec = __ts.tv_sec; + ts.nsec = __ts.tv_nsec; + + pps_event(port->pps_source, &ts, PPS_CAPTUREASSERT, port); + dev_dbg(port->dev, "PPS assert at %lu on source #%d\n", + jiffies, port->pps_source); +#endif + parport_ieee1284_interrupt (port); read_lock(&port->cad_lock); if (port->cad && port->cad->irq_func) diff --git a/include/linux/pps.h b/include/linux/pps.h new file mode 100644 index 0000000..8a92204 --- /dev/null +++ b/include/linux/pps.h @@ -0,0 +1,195 @@ +/* + * PPS API kernel header + * + * + * Copyright (C) 2005-2009 Rodolfo Giometti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#ifndef _PPS_H_ +#define _PPS_H_ + +#define PPS_VERSION "5.3.5" +#define PPS_MAX_SOURCES 16 /* should be enough... */ + +/* Implementation note: the logical states ``assert'' and ``clear'' + * are implemented in terms of the chip register, i.e. ``assert'' + * means the bit is set. */ + +/* + * 3.2 New data structures + */ + +#define PPS_API_VERS_1 1 +#define PPS_API_VERS PPS_API_VERS_1 /* we use API version 1 */ +#define PPS_MAX_NAME_LEN 32 + +/* 32-bit vs. 64-bit compatibility. + * + * 0n i386, the alignment of a uint64_t is only 4 bytes, while on most other + * architectures it's 8 bytes. On i386, there will be no padding between the + * two consecutive 'struct pps_ktime' members of struct pps_kinfo and struct + * pps_kparams. But on most platforms there will be padding to ensure correct + * alignment. + * + * The simple fix is probably to add an explicit padding. + * [David Woodhouse] + */ +struct pps_ktime { + __s64 sec; + __s32 nsec; + __u32 flags; +}; +#define PPS_TIME_INVALID (1<<0) /* used to specify timeout==NULL */ + +struct pps_kinfo { + __u32 assert_sequence; /* seq. num. of assert event */ + __u32 clear_sequence; /* seq. num. of clear event */ + struct pps_ktime assert_tu; /* time of assert event */ + struct pps_ktime clear_tu; /* time of clear event */ + int current_mode; /* current mode bits */ +}; + +struct pps_kparams { + int api_version; /* API version # */ + int mode; /* mode bits */ + struct pps_ktime assert_off_tu; /* offset compensation for assert */ + struct pps_ktime clear_off_tu; /* offset compensation for clear */ +}; + +/* + * 3.3 Mode bit definitions + */ + +/* Device/implementation parameters */ +#define PPS_CAPTUREASSERT 0x01 /* capture assert events */ +#define PPS_CAPTURECLEAR 0x02 /* capture clear events */ +#define PPS_CAPTUREBOTH 0x03 /* capture assert and clear events */ + +#define PPS_OFFSETASSERT 0x10 /* apply compensation for assert ev. */ +#define PPS_OFFSETCLEAR 0x20 /* apply compensation for clear ev. */ + +#define PPS_CANWAIT 0x100 /* can we wait for an event? */ +#define PPS_CANPOLL 0x200 /* bit reserved for future use */ + +/* Kernel actions */ +#define PPS_ECHOASSERT 0x40 /* feed back assert event to output */ +#define PPS_ECHOCLEAR 0x80 /* feed back clear event to output */ + +/* Timestamp formats */ +#define PPS_TSFMT_TSPEC 0x1000 /* select timespec format */ +#define PPS_TSFMT_NTPFP 0x2000 /* select NTP format */ + +/* + * 3.4.4 New functions: disciplining the kernel timebase + */ + +/* Kernel consumers */ +#define PPS_KC_HARDPPS 0 /* hardpps() (or equivalent) */ +#define PPS_KC_HARDPPS_PLL 1 /* hardpps() constrained to + use a phase-locked loop */ +#define PPS_KC_HARDPPS_FLL 2 /* hardpps() constrained to + use a frequency-locked loop */ +/* + * Here begins the implementation-specific part! + */ + +struct pps_fdata { + struct pps_kinfo info; + struct pps_ktime timeout; +}; + +#include + +#define PPS_GETPARAMS _IOR('p', 0xa1, struct pps_kparams *) +#define PPS_SETPARAMS _IOW('p', 0xa2, struct pps_kparams *) +#define PPS_GETCAP _IOR('p', 0xa3, int *) +#define PPS_FETCH _IOWR('p', 0xa4, struct pps_fdata *) + +#ifdef __KERNEL__ + +#include +#include +#include + +/* + * Global defines + */ + +/* The specific PPS source info */ +struct pps_source_info { + char name[PPS_MAX_NAME_LEN]; /* simbolic name */ + char path[PPS_MAX_NAME_LEN]; /* path of connected device */ + int mode; /* PPS's allowed mode */ + + void (*echo)(int source, int event, void *data); /* PPS echo function */ + + struct module *owner; + struct device *dev; +}; + +/* The main struct */ +struct pps_device { + struct pps_source_info info; /* PSS source info */ + + struct pps_kparams params; /* PPS's current params */ + + __u32 assert_sequence; /* PPS' assert event seq # */ + __u32 clear_sequence; /* PPS' clear event seq # */ + struct pps_ktime assert_tu; + struct pps_ktime clear_tu; + int current_mode; /* PPS mode at event time */ + + int go; /* PPS event is arrived? */ + wait_queue_head_t queue; /* PPS event queue */ + + unsigned int id; /* PPS source unique ID */ + struct cdev cdev; + struct device *dev; + int devno; + struct fasync_struct *async_queue; /* fasync method */ + spinlock_t lock; + + atomic_t usage; /* usage count */ +}; + +/* + * Global variables + */ + +extern spinlock_t pps_idr_lock; +extern struct idr pps_idr; +extern struct timespec pps_irq_ts[]; + +extern struct device_attribute pps_attrs[]; + +/* + * Exported functions + */ + +struct pps_device *pps_get_source(int source); +extern void pps_put_source(struct pps_device *pps); +extern int pps_register_source(struct pps_source_info *info, + int default_params); +extern void pps_unregister_source(int source); +extern int pps_register_cdev(struct pps_device *pps); +extern void pps_unregister_cdev(struct pps_device *pps); +extern void pps_event(int source, struct pps_ktime *ts, int event, void *data); + +#endif /* __KERNEL__ */ + +#endif /* _PPS_H_ */ diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 4e4f127..eac89e6 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -168,6 +168,7 @@ #include #include #include +#include struct uart_port; struct uart_info; @@ -491,9 +492,17 @@ static inline void uart_handle_dcd_change(struct uart_port *port, unsigned int status) { struct uart_info *info = port->info; + struct tty_ldisc *ld = tty_ldisc_ref(info->port.tty); + struct timespec ts; + + if (ld && ld->ops->dcd_change) +#ifdef CONFIG_PPS_IRQ_EVENTS + ts = pps_irq_ts[port->irq]; +#else + getnstimeofday(&ts); +#endif port->icount.dcd++; - #ifdef CONFIG_HARD_PPS if ((port->flags & UPF_HARDPPS_CD) && status) hardpps(); @@ -505,6 +514,11 @@ uart_handle_dcd_change(struct uart_port *port, unsigned int status) else if (info->port.tty) tty_hangup(info->port.tty); } + + if (ld && ld->ops->dcd_change) + ld->ops->dcd_change(info->port.tty, status, &ts); + if (ld) + tty_ldisc_deref(ld); } /** diff --git a/include/linux/tty.h b/include/linux/tty.h index 3b8121d..190ad4a 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -432,6 +433,21 @@ extern void tty_ldisc_begin(void); /* This last one is just for the tty layer internals and shouldn't be used elsewhere */ extern void tty_ldisc_enable(struct tty_struct *tty); +extern void n_tty_flush_buffer(struct tty_struct *tty); +extern ssize_t n_tty_chars_in_buffer(struct tty_struct *tty); +extern void n_tty_write_wakeup(struct tty_struct *tty); +extern void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count); +extern void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old); +extern void n_tty_close(struct tty_struct *tty); +extern int n_tty_open(struct tty_struct *tty); +extern ssize_t n_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr); +extern ssize_t n_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr); +extern unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, + poll_table *wait); + /* n_tty.c */ extern struct tty_ldisc_ops tty_ldisc_N_TTY; diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h index 40f38d8..526fbf4 100644 --- a/include/linux/tty_ldisc.h +++ b/include/linux/tty_ldisc.h @@ -99,6 +99,12 @@ * cease I/O to the tty driver. Can sleep. The driver should * seek to perform this action quickly but should wait until * any pending driver I/O is completed. + * + * void (*dcd_change)(struct tty_struct *tty, unsigned int status, + * struct timespec *ts) + * + * Tells the discipline that the DCD pin has changed its status and + * the relative timestamp. Pointer ts can be NULL. */ #include @@ -136,6 +142,8 @@ struct tty_ldisc_ops { void (*receive_buf)(struct tty_struct *, const unsigned char *cp, char *fp, int count); void (*write_wakeup)(struct tty_struct *); + void (*dcd_change)(struct tty_struct *, unsigned int, + struct timespec *); struct module *owner;