* Re: [PATCH 08/12] ptp: Added a brand new class driver for ptp clocks.
From: Grant Likely @ 2010-06-15 17:00 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, devicetree-discuss, Thomas Gleixner, linuxppc-dev,
linux-arm-kernel, Krzysztof Halasa
In-Reply-To: <4a030d2bace90f089f2f3f61496b918c6f1dfb52.1276615626.git.richard.cochran@omicron.at>
On Tue, Jun 15, 2010 at 10:09 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> This patch adds an infrastructure for hardware clocks that implement
> IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
> registration method to particular hardware clock drivers. Each clock is
> exposed to user space as a character device with ioctls that allow tuning
> of the PTP clock.
>
> Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
> ---
> Documentation/ptp/ptp.txt | 95 +++++++
> Documentation/ptp/testptp.c | 269 ++++++++++++++++++++
> Documentation/ptp/testptp.mk | 33 +++
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/ptp/Kconfig | 26 ++
> drivers/ptp/Makefile | 5 +
> drivers/ptp/ptp_clock.c | 514 ++++++++++++++++++++++++++++++++++++++
> include/linux/Kbuild | 1 +
> include/linux/ptp_clock.h | 79 ++++++
> include/linux/ptp_clock_kernel.h | 137 ++++++++++
> 11 files changed, 1162 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/ptp/ptp.txt
> create mode 100644 Documentation/ptp/testptp.c
> create mode 100644 Documentation/ptp/testptp.mk
> create mode 100644 drivers/ptp/Kconfig
> create mode 100644 drivers/ptp/Makefile
> create mode 100644 drivers/ptp/ptp_clock.c
> create mode 100644 include/linux/ptp_clock.h
> create mode 100644 include/linux/ptp_clock_kernel.h
>
> diff --git a/Documentation/ptp/ptp.txt b/Documentation/ptp/ptp.txt
> new file mode 100644
> index 0000000..46858b3
> --- /dev/null
> +++ b/Documentation/ptp/ptp.txt
> @@ -0,0 +1,95 @@
> +
> +* PTP infrastructure for Linux
> +
> + This patch set introduces support for IEEE 1588 PTP clocks in
> + Linux. Together with the SO_TIMESTAMPING socket options, this
> + presents a standardized method for developing PTP user space
> + programs, synchronizing Linux with external clocks, and using the
> + ancillary features of PTP hardware clocks.
> +
> + A new class driver exports a kernel interface for specific clock
> + drivers and a user space interface. The infrastructure supports a
> + complete set of PTP functionality.
> +
> + + Basic clock operations
> + - Set time
> + - Get time
> + - Shift the clock by a given offset atomically
> + - Adjust clock frequency
> +
> + + Ancillary clock features
> + - One short or periodic alarms, with signal delivery to user program
> + - Time stamp external events
> + - Period output signals configurable from user space
> + - Synchronization of the Linux system time via the PPS subsystem
> +
> +** PTP kernel API
> +
> + A PTP clock driver registers itself with the class driver. The
> + class driver handles all of the dealings with user space. The
> + author of a clock driver need only implement the details of
> + programming the clock hardware. The clock driver notifies the class
> + driver of asynchronous events (alarms and external time stamps) via
> + a simple message passing interface.
> +
> + The class driver supports multiple PTP clock drivers. In normal use
> + cases, only one PTP clock is needed. However, for testing and
> + development, it can be useful to have more than one clock in a
> + single system, in order to allow performance comparisons.
> +
> +** PTP user space API
> +
> + The class driver creates a character device for each registered PTP
> + clock. User space programs may control the clock using standardized
> + ioctls. A program may query, enable, configure, and disable the
> + ancillary clock features. User space can receive time stamped
> + events via blocking read() and poll(). One shot and periodic
> + signals may be configured via an ioctl API with semantics similar
> + to the POSIX timer_settime() system call.
> +
> + As an real life example, the following two patches for ptpd version
> + 1.0.0 demonstrate how the API works.
> +
> + https://sourceforge.net/tracker/?func=detail&aid=2992845&group_id=139814&atid=744634
> +
> + https://sourceforge.net/tracker/?func=detail&aid=2992847&group_id=139814&atid=744634
Question from an ignorant reviewer: Why a new interface instead of
working with the existing high resolution timers infrastructure?
g.
> +
> +** Writing clock drivers
> +
> + Clock drivers include include/linux/ptp_clock_kernel.h and register
> + themselves by presenting a 'struct ptp_clock_info' to the
> + registration method. Clock drivers must implement all of the
> + functions in the interface. If a clock does not offer a particular
> + ancillary feature, then the driver should just return -EOPNOTSUPP
> + from those functions.
> +
> + Drivers must ensure that all of the methods in interface are
> + reentrant. Since most hardware implementations treat the time value
> + as a 64 bit integer accessed as two 32 bit registers, drivers
> + should use spin_lock_irqsave/spin_unlock_irqrestore to protect
> + against concurrent access. This locking cannot be accomplished in
> + class driver, since the lock may also be needed by the clock
> + driver's interrupt service routine.
> +
> +** Supported hardware
> +
> + + Standard Linux system timer
> + - No special PTP features
> + - For use with software time stamping
> +
> + + Freescale eTSEC gianfar
> + - 2 Time stamp external triggers, programmable polarity (opt. interrupt)
> + - 2 Alarm registers (optional interrupt)
> + - 3 Periodic signals (optional interrupt)
> +
> + + National DP83640
> + - 6 GPIOs programmable as inputs or outputs
> + - 6 GPIOs with dedicated functions (LED/JTAG/clock) can also be
> + used as general inputs or outputs
> + - GPIO inputs can time stamp external triggers
> + - GPIO outputs can produce periodic signals
> + - 1 interrupt pin
> +
> + + Intel IXP465
> + - Auxiliary Slave/Master Mode Snapshot (optional interrupt)
> + - Target Time (optional interrupt)
> diff --git a/Documentation/ptp/testptp.c b/Documentation/ptp/testptp.c
> new file mode 100644
> index 0000000..e30f758
> --- /dev/null
> +++ b/Documentation/ptp/testptp.c
> @@ -0,0 +1,269 @@
> +/*
> + * PTP 1588 clock support - User space test program
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 <errno.h>
> +#include <fcntl.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include <sys/stat.h>
> +#include <sys/time.h>
> +#include <sys/types.h>
> +#include <time.h>
> +#include <unistd.h>
> +
> +#include <linux/ptp_clock.h>
> +
> +static void handle_alarm(int s)
> +{
> + printf("received signal %d\n", s);
> +}
> +
> +static int install_handler(int signum, void (*handler)(int))
> +{
> + struct sigaction action;
> + sigset_t mask;
> +
> + /* Unblock the signal. */
> + sigemptyset(&mask);
> + sigaddset(&mask, signum);
> + sigprocmask(SIG_UNBLOCK, &mask, NULL);
> +
> + /* Install the signal handler. */
> + action.sa_handler = handler;
> + action.sa_flags = 0;
> + sigemptyset(&action.sa_mask);
> + sigaction(signum, &action, NULL);
> +
> + return 0;
> +}
> +
> +static void usage(char *progname)
> +{
> + fprintf(stderr,
> + "usage: %s [options]\n"
> + " -a val request a one-shot alarm after 'val' seconds\n"
> + " -A val request a periodic alarm every 'val' seconds\n"
> + " -c query the ptp clock's capabilities\n"
> + " -d name device to open\n"
> + " -e val read 'val' external time stamp events\n"
> + " -f val adjust the ptp clock frequency by 'val' PPB\n"
> + " -g get the ptp clock time\n"
> + " -h prints this message\n"
> + " -s set the ptp clock time from the system time\n"
> + " -t val shift the ptp clock time by 'val' seconds\n"
> + " -v query the ptp clock api version\n",
> + progname);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + struct ptp_clock_caps caps;
> + struct ptp_clock_timer timer;
> + struct ptp_extts_event event;
> + struct ptp_clock_request request;
> + struct timespec ts;
> + char *progname;
> + int c, cnt, fd, val = 0;
> +
> + char *device = "/dev/ptp_clock_0";
> + int adjfreq = 0x7fffffff;
> + int adjtime = 0;
> + int capabilities = 0;
> + int extts = 0;
> + int gettime = 0;
> + int oneshot = 0;
> + int periodic = 0;
> + int settime = 0;
> + int version = 0;
> +
> + progname = strrchr(argv[0], '/');
> + progname = progname ? 1+progname : argv[0];
> + while (EOF != (c = getopt(argc, argv, "a:A:cd:e:f:ghst:v"))) {
> + switch (c) {
> + case 'a':
> + oneshot = atoi(optarg);
> + break;
> + case 'A':
> + periodic = atoi(optarg);
> + break;
> + case 'c':
> + capabilities = 1;
> + break;
> + case 'd':
> + device = optarg;
> + break;
> + case 'e':
> + extts = atoi(optarg);
> + break;
> + case 'f':
> + adjfreq = atoi(optarg);
> + break;
> + case 'g':
> + gettime = 1;
> + break;
> + case 's':
> + settime = 1;
> + break;
> + case 't':
> + adjtime = atoi(optarg);
> + break;
> + case 'v':
> + version = 1;
> + break;
> + case 'h':
> + usage(progname);
> + return 0;
> + case '?':
> + default:
> + usage(progname);
> + return -1;
> + }
> + }
> +
> + fd = open(device, O_RDWR);
> + if (fd < 0) {
> + fprintf(stderr, "cannot open %s: %s", device, strerror(errno));
> + return -1;
> + }
> +
> + if (version) {
> + if (ioctl(fd, PTP_CLOCK_APIVERS, &val)) {
> + perror("PTP_CLOCK_APIVERS");
> + } else {
> + printf("version = 0x%08x\n", val);
> + }
> + }
> +
> + if (capabilities) {
> + if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) {
> + perror("PTP_CLOCK_GETCAPS");
> + } else {
> + printf("capabilities:\n"
> + " %d maximum frequency adjustment (PPB)\n"
> + " %d programmable alarms\n"
> + " %d external time stamp channels\n"
> + " %d programmable periodic signals\n"
> + " %d pulse per second\n",
> + caps.max_adj,
> + caps.n_alarm,
> + caps.n_ext_ts,
> + caps.n_per_out,
> + caps.pps);
> + }
> + }
> +
> + if (0x7fffffff != adjfreq) {
> + if (ioctl(fd, PTP_CLOCK_ADJFREQ, adjfreq)) {
> + perror("PTP_CLOCK_ADJFREQ");
> + } else {
> + puts("frequency adjustment okay");
> + }
> + }
> +
> + if (adjtime) {
> + ts.tv_sec = adjtime;
> + ts.tv_nsec = 0;
> + if (ioctl(fd, PTP_CLOCK_ADJTIME, &ts)) {
> + perror("PTP_CLOCK_ADJTIME");
> + } else {
> + puts("time shift okay");
> + }
> + }
> +
> + if (gettime) {
> + if (ioctl(fd, PTP_CLOCK_GETTIME, &ts)) {
> + perror("PTP_CLOCK_GETTIME");
> + } else {
> + printf("clock time: %ld.%09ld or %s",
> + ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec));
> + }
> + }
> +
> + if (settime) {
> + clock_gettime(CLOCK_REALTIME, &ts);
> + if (ioctl(fd, PTP_CLOCK_SETTIME, &ts)) {
> + perror("PTP_CLOCK_SETTIME");
> + } else {
> + puts("set time okay");
> + }
> + }
> +
> + if (extts) {
> + memset(&request, 0, sizeof(request));
> + request.type = PTP_REQUEST_EXTTS;
> + request.index = 0;
> + request.flags = PTP_ENABLE_FEATURE;
> + if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
> + perror("PTP_FEATURE_REQUEST");
> + extts = 0;
> + } else {
> + puts("set timer okay");
> + }
> + for (; extts; extts--) {
> + cnt = read(fd, &event, sizeof(event));
> + if (cnt != sizeof(event)) {
> + perror("read");
> + break;
> + }
> + printf("event index %d at %ld.%09ld\n", event.index,
> + event.ts.tv_sec, event.ts.tv_nsec);
> + }
> + /* Disable the feature again. */
> + request.flags = 0;
> + if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
> + perror("PTP_FEATURE_REQUEST");
> + }
> + }
> +
> + if (oneshot) {
> + install_handler(SIGALRM, handle_alarm);
> + memset(&timer, 0, sizeof(timer));
> + timer.signum = SIGALRM;
> + timer.tsp.it_value.tv_sec = oneshot;
> + if (ioctl(fd, PTP_CLOCK_SETTIMER, &timer)) {
> + perror("PTP_CLOCK_SETTIMER");
> + } else {
> + puts("set timer okay");
> + }
> + pause();
> + }
> +
> + if (periodic) {
> + install_handler(SIGALRM, handle_alarm);
> + memset(&timer, 0, sizeof(timer));
> + timer.signum = SIGALRM;
> + timer.tsp.it_value.tv_sec = periodic;
> + timer.tsp.it_interval.tv_sec = periodic;
> + if (ioctl(fd, PTP_CLOCK_SETTIMER, &timer)) {
> + perror("PTP_CLOCK_SETTIMER");
> + } else {
> + puts("set timer okay");
> + }
> + while (1) {
> + pause();
> + }
> + }
> +
> + close(fd);
> + return 0;
> +}
> diff --git a/Documentation/ptp/testptp.mk b/Documentation/ptp/testptp.mk
> new file mode 100644
> index 0000000..4ef2d97
> --- /dev/null
> +++ b/Documentation/ptp/testptp.mk
> @@ -0,0 +1,33 @@
> +# PTP 1588 clock support - User space test program
> +#
> +# Copyright (C) 2010 OMICRON electronics GmbH
> +#
> +# 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.
> +
> +CC = $(CROSS_COMPILE)gcc
> +INC = -I$(KBUILD_OUTPUT)/usr/include
> +CFLAGS = -Wall $(INC)
> +LDLIBS = -lrt
> +PROGS = testptp
> +
> +all: $(PROGS)
> +
> +testptp: testptp.o
> +
> +clean:
> + rm -f testptp.o
> +
> +distclean: clean
> + rm -f $(PROGS)
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index a2b902f..774fbd7 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -52,6 +52,8 @@ source "drivers/spi/Kconfig"
>
> source "drivers/pps/Kconfig"
>
> +source "drivers/ptp/Kconfig"
> +
> source "drivers/gpio/Kconfig"
>
> source "drivers/w1/Kconfig"
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 91874e0..6d12b48 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -76,6 +76,7 @@ obj-$(CONFIG_I2O) += message/
> obj-$(CONFIG_RTC_LIB) += rtc/
> obj-y += i2c/ media/
> obj-$(CONFIG_PPS) += pps/
> +obj-$(CONFIG_PTP_1588_CLOCK) += ptp/
> obj-$(CONFIG_W1) += w1/
> obj-$(CONFIG_POWER_SUPPLY) += power/
> obj-$(CONFIG_HWMON) += hwmon/
> diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
> new file mode 100644
> index 0000000..c80a25b
> --- /dev/null
> +++ b/drivers/ptp/Kconfig
> @@ -0,0 +1,26 @@
> +#
> +# PTP clock support configuration
> +#
> +
> +menu "PTP clock support"
> +
> +config PTP_1588_CLOCK
> + tristate "PTP clock support"
> + depends on EXPERIMENTAL
> + help
> + The IEEE 1588 standard defines a method to precisely
> + synchronize distributed clocks over Ethernet networks. The
> + standard defines a Precision Time Protocol (PTP), which can
> + be used to achieve synchronization within a few dozen
> + microseconds. In addition, with the help of special hardware
> + time stamping units, it can be possible to achieve
> + synchronization to within a few hundred nanoseconds.
> +
> + This driver adds support for PTP clocks as character
> + devices. If you want to use a PTP clock, then you should
> + also enable at least one clock driver as well.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called ptp_clock.
> +
> +endmenu
> diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
> new file mode 100644
> index 0000000..b86695c
> --- /dev/null
> +++ b/drivers/ptp/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for PTP 1588 clock support.
> +#
> +
> +obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
> diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
> new file mode 100644
> index 0000000..4753bf3
> --- /dev/null
> +++ b/drivers/ptp/ptp_clock.c
> @@ -0,0 +1,514 @@
> +/*
> + * PTP 1588 clock support
> + *
> + * Partially adapted from the Linux PPS driver.
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 <linux/bitops.h>
> +#include <linux/cdev.h>
> +#include <linux/device.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/poll.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#include <linux/ptp_clock_kernel.h>
> +#include <linux/ptp_clock.h>
> +
> +#define PTP_MAX_ALARMS 4
> +#define PTP_MAX_CLOCKS BITS_PER_LONG
> +#define PTP_MAX_TIMESTAMPS 128
> +
> +struct alarm {
> + struct pid *pid;
> + int sig;
> +};
> +
> +struct timestamp_event_queue {
> + struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
> + int head;
> + int tail;
> + int overflow;
> +};
> +
> +struct ptp_clock {
> + struct list_head list;
> + struct cdev cdev;
> + struct device *dev;
> + struct ptp_clock_info *info;
> + dev_t devid;
> + int index; /* index into clocks.map, also the minor number */
> +
> + struct alarm alarm[PTP_MAX_ALARMS];
> + struct mutex alarm_mux; /* one process at a time setting an alarm */
> +
> + struct timestamp_event_queue tsevq; /* simple fifo for time stamps */
> + struct mutex tsevq_mux; /* one process at a time reading the fifo */
> + wait_queue_head_t tsev_wq;
> +};
> +
> +/* private globals */
> +
> +static const struct file_operations ptp_fops;
> +static dev_t ptp_devt;
> +static struct class *ptp_class;
> +
> +static struct {
> + struct list_head list;
> + DECLARE_BITMAP(map, PTP_MAX_CLOCKS);
> +} clocks;
> +static DEFINE_SPINLOCK(clocks_lock); /* protects 'clocks' */
> +
> +/* time stamp event queue operations */
> +
> +static inline int queue_cnt(struct timestamp_event_queue *q)
> +{
> + int cnt = q->tail - q->head;
> + return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt;
> +}
> +
> +static inline int queue_free(struct timestamp_event_queue *q)
> +{
> + return PTP_MAX_TIMESTAMPS - queue_cnt(q) - 1;
> +}
> +
> +static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
> + struct ptp_clock_event *src)
> +{
> + struct ptp_extts_event *dst;
> + u32 remainder;
> +
> + dst = &queue->buf[queue->tail];
> +
> + dst->index = src->index;
> + dst->ts.tv_sec = div_u64_rem(src->timestamp, 1000000000, &remainder);
> + dst->ts.tv_nsec = remainder;
> +
> + if (!queue_free(queue))
> + queue->overflow++;
> +
> + queue->tail = (queue->tail + 1) % PTP_MAX_TIMESTAMPS;
> +}
> +
> +/* public interface */
> +
> +struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info)
> +{
> + struct ptp_clock *ptp;
> + int err = 0, index, major = MAJOR(ptp_devt);
> + unsigned long flags;
> +
> + if (info->n_alarm > PTP_MAX_ALARMS)
> + return ERR_PTR(-EINVAL);
> +
> + /* Find a free clock slot and reserve it. */
> + err = -EBUSY;
> + spin_lock_irqsave(&clocks_lock, flags);
> + index = find_first_zero_bit(clocks.map, PTP_MAX_CLOCKS);
> + if (index < PTP_MAX_CLOCKS) {
> + set_bit(index, clocks.map);
> + spin_unlock_irqrestore(&clocks_lock, flags);
> + } else {
> + spin_unlock_irqrestore(&clocks_lock, flags);
> + goto no_clock;
> + }
> +
> + /* Initialize a clock structure. */
> + err = -ENOMEM;
> + ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL);
> + if (ptp == NULL)
> + goto no_memory;
> +
> + ptp->info = info;
> + ptp->devid = MKDEV(major, index);
> + ptp->index = index;
> + mutex_init(&ptp->alarm_mux);
> + mutex_init(&ptp->tsevq_mux);
> + init_waitqueue_head(&ptp->tsev_wq);
> +
> + /* Create a new device in our class. */
> + ptp->dev = device_create(ptp_class, NULL, ptp->devid, ptp,
> + "ptp_clock_%d", ptp->index);
> + if (IS_ERR(ptp->dev))
> + goto no_device;
> +
> + dev_set_drvdata(ptp->dev, ptp);
> +
> + /* Register a character device. */
> + cdev_init(&ptp->cdev, &ptp_fops);
> + ptp->cdev.owner = info->owner;
> + err = cdev_add(&ptp->cdev, ptp->devid, 1);
> + if (err)
> + goto no_cdev;
> +
> + /* Clock is ready, add it into the list. */
> + spin_lock_irqsave(&clocks_lock, flags);
> + list_add(&ptp->list, &clocks.list);
> + spin_unlock_irqrestore(&clocks_lock, flags);
> +
> + return ptp;
> +
> +no_cdev:
> + device_destroy(ptp_class, ptp->devid);
> +no_device:
> + mutex_destroy(&ptp->alarm_mux);
> + mutex_destroy(&ptp->tsevq_mux);
> + kfree(ptp);
> +no_memory:
> + spin_lock_irqsave(&clocks_lock, flags);
> + clear_bit(index, clocks.map);
> + spin_unlock_irqrestore(&clocks_lock, flags);
> +no_clock:
> + return ERR_PTR(err);
> +}
> +EXPORT_SYMBOL(ptp_clock_register);
> +
> +int ptp_clock_unregister(struct ptp_clock *ptp)
> +{
> + unsigned long flags;
> +
> + /* Release the clock's resources. */
> + cdev_del(&ptp->cdev);
> + device_destroy(ptp_class, ptp->devid);
> + mutex_destroy(&ptp->alarm_mux);
> + mutex_destroy(&ptp->tsevq_mux);
> +
> + /* Remove the clock from the list. */
> + spin_lock_irqsave(&clocks_lock, flags);
> + list_del(&ptp->list);
> + clear_bit(ptp->index, clocks.map);
> + spin_unlock_irqrestore(&clocks_lock, flags);
> +
> + kfree(ptp);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(ptp_clock_unregister);
> +
> +void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
> +{
> + switch (event->type) {
> +
> + case PTP_CLOCK_ALARM:
> + kill_pid(ptp->alarm[event->index].pid,
> + ptp->alarm[event->index].sig, 1);
> + break;
> +
> + case PTP_CLOCK_EXTTS:
> + enqueue_external_timestamp(&ptp->tsevq, event);
> + wake_up_interruptible(&ptp->tsev_wq);
> + break;
> +
> + case PTP_CLOCK_PPS:
> + break;
> + }
> +}
> +EXPORT_SYMBOL(ptp_clock_event);
> +
> +/* character device operations */
> +
> +static int ptp_ioctl(struct inode *node, struct file *fp,
> + unsigned int cmd, unsigned long arg)
> +{
> + struct ptp_clock_caps caps;
> + struct ptp_clock_request req;
> + struct ptp_clock_timer timer;
> + struct ptp_clock *ptp = fp->private_data;
> + struct ptp_clock_info *ops = ptp->info;
> + void *priv = ops->priv;
> + struct timespec ts;
> + int flags, index;
> + int err = 0;
> +
> + switch (cmd) {
> +
> + case PTP_CLOCK_APIVERS:
> + err = put_user(PTP_CLOCK_VERSION, (u32 __user *)arg);
> + break;
> +
> + case PTP_CLOCK_ADJFREQ:
> + if (!capable(CAP_SYS_TIME))
> + return -EPERM;
> + err = ops->adjfreq(priv, arg);
> + break;
> +
> + case PTP_CLOCK_ADJTIME:
> + if (!capable(CAP_SYS_TIME))
> + return -EPERM;
> + if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
> + err = -EFAULT;
> + else
> + err = ops->adjtime(priv, &ts);
> + break;
> +
> + case PTP_CLOCK_GETTIME:
> + err = ops->gettime(priv, &ts);
> + if (err)
> + break;
> + err = copy_to_user((void __user *)arg, &ts, sizeof(ts));
> + break;
> +
> + case PTP_CLOCK_SETTIME:
> + if (!capable(CAP_SYS_TIME))
> + return -EPERM;
> + if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
> + err = -EFAULT;
> + else
> + err = ops->settime(priv, &ts);
> + break;
> +
> + case PTP_CLOCK_GETCAPS:
> + memset(&caps, 0, sizeof(caps));
> + caps.max_adj = ptp->info->max_adj;
> + caps.n_alarm = ptp->info->n_alarm;
> + caps.n_ext_ts = ptp->info->n_ext_ts;
> + caps.n_per_out = ptp->info->n_per_out;
> + caps.pps = ptp->info->pps;
> + err = copy_to_user((void __user *)arg, &caps, sizeof(caps));
> + break;
> +
> + case PTP_CLOCK_GETTIMER:
> + if (copy_from_user(&timer, (void __user *)arg, sizeof(timer))) {
> + err = -EFAULT;
> + break;
> + }
> + index = timer.alarm_index;
> + if (index < 0 || index >= ptp->info->n_alarm) {
> + err = -EINVAL;
> + break;
> + }
> + err = ops->gettimer(priv, index, &timer.tsp);
> + if (err)
> + break;
> + err = copy_to_user((void __user *)arg, &timer, sizeof(timer));
> + break;
> +
> + case PTP_CLOCK_SETTIMER:
> + if (copy_from_user(&timer, (void __user *)arg, sizeof(timer))) {
> + err = -EFAULT;
> + break;
> + }
> + index = timer.alarm_index;
> + if (index < 0 || index >= ptp->info->n_alarm) {
> + err = -EINVAL;
> + break;
> + }
> + if (!valid_signal(timer.signum))
> + return -EINVAL;
> + flags = timer.flags;
> + if (flags & (flags != TIMER_ABSTIME)) {
> + err = -EINVAL;
> + break;
> + }
> + if (mutex_lock_interruptible(&ptp->alarm_mux))
> + return -ERESTARTSYS;
> +
> + if (ptp->alarm[index].pid)
> + put_pid(ptp->alarm[index].pid);
> +
> + ptp->alarm[index].pid = get_pid(task_pid(current));
> + ptp->alarm[index].sig = timer.signum;
> + err = ops->settimer(priv, index, flags, &timer.tsp);
> +
> + mutex_unlock(&ptp->alarm_mux);
> + break;
> +
> + case PTP_FEATURE_REQUEST:
> + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) {
> + err = -EFAULT;
> + break;
> + }
> + switch (req.type) {
> + case PTP_REQUEST_EXTTS:
> + case PTP_REQUEST_PEROUT:
> + break;
> + case PTP_REQUEST_PPS:
> + if (!capable(CAP_SYS_TIME))
> + return -EPERM;
> + break;
> + default:
> + err = -EINVAL;
> + break;
> + }
> + if (err)
> + break;
> + err = ops->enable(priv, &req,
> + req.flags & PTP_ENABLE_FEATURE ? 1 : 0);
> + break;
> +
> + default:
> + err = -ENOTTY;
> + break;
> + }
> + return err;
> +}
> +
> +static int ptp_open(struct inode *inode, struct file *fp)
> +{
> + struct ptp_clock *ptp;
> + ptp = container_of(inode->i_cdev, struct ptp_clock, cdev);
> +
> + fp->private_data = ptp;
> +
> + return 0;
> +}
> +
> +static unsigned int ptp_poll(struct file *fp, poll_table *wait)
> +{
> + struct ptp_clock *ptp = fp->private_data;
> +
> + poll_wait(fp, &ptp->tsev_wq, wait);
> +
> + return queue_cnt(&ptp->tsevq) ? POLLIN : 0;
> +}
> +
> +static ssize_t ptp_read(struct file *fp, char __user *buf,
> + size_t cnt, loff_t *off)
> +{
> + struct ptp_clock *ptp = fp->private_data;
> + struct timestamp_event_queue *queue = &ptp->tsevq;
> + struct ptp_extts_event *event;
> + size_t qcnt;
> +
> + if (mutex_lock_interruptible(&ptp->tsevq_mux))
> + return -ERESTARTSYS;
> +
> + cnt = cnt / sizeof(struct ptp_extts_event);
> +
> + if (wait_event_interruptible(ptp->tsev_wq,
> + (qcnt = queue_cnt(&ptp->tsevq)))) {
> + mutex_unlock(&ptp->tsevq_mux);
> + return -ERESTARTSYS;
> + }
> +
> + if (cnt > qcnt)
> + cnt = qcnt;
> +
> + event = &queue->buf[queue->head];
> +
> + if (copy_to_user(buf, event, cnt * sizeof(struct ptp_extts_event))) {
> + mutex_unlock(&ptp->tsevq_mux);
> + return -EFAULT;
> + }
> + queue->head = (queue->head + cnt) % PTP_MAX_TIMESTAMPS;
> +
> + mutex_unlock(&ptp->tsevq_mux);
> +
> + return cnt * sizeof(struct ptp_extts_event);
> +}
> +
> +static int ptp_release(struct inode *inode, struct file *fp)
> +{
> + struct ptp_clock *ptp;
> + struct itimerspec ts = {
> + {0, 0}, {0, 0}
> + };
> + int i;
> +
> + ptp = container_of(inode->i_cdev, struct ptp_clock, cdev);
> +
> + for (i = 0; i < ptp->info->n_alarm; i++) {
> + if (ptp->alarm[i].pid) {
> + ptp->info->settimer(ptp->info->priv, i, 0, &ts);
> + put_pid(ptp->alarm[i].pid);
> + ptp->alarm[i].pid = NULL;
> + }
> + }
> + return 0;
> +}
> +
> +static const struct file_operations ptp_fops = {
> + .owner = THIS_MODULE,
> + .ioctl = ptp_ioctl,
> + .open = ptp_open,
> + .poll = ptp_poll,
> + .read = ptp_read,
> + .release = ptp_release,
> +};
> +
> +/* sysfs */
> +
> +static ssize_t ptp_show_status(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ptp_clock *ptp = dev_get_drvdata(dev);
> + return sprintf(buf,
> + "maximum adjustment: %d\n"
> + "programmable alarms: %d\n"
> + "external timestamps: %d\n"
> + "periodic outputs: %d\n"
> + "has pps: %d\n"
> + "device index: %d\n",
> + ptp->info->max_adj,
> + ptp->info->n_alarm,
> + ptp->info->n_ext_ts,
> + ptp->info->n_per_out,
> + ptp->info->pps,
> + ptp->index);
> +}
> +
> +struct device_attribute ptp_attrs[] = {
> + __ATTR(capabilities, S_IRUGO, ptp_show_status, NULL),
> + __ATTR_NULL,
> +};
> +
> +/* module operations */
> +
> +static void __exit ptp_exit(void)
> +{
> + class_destroy(ptp_class);
> + unregister_chrdev_region(ptp_devt, PTP_MAX_CLOCKS);
> +}
> +
> +static int __init ptp_init(void)
> +{
> + int err;
> +
> + INIT_LIST_HEAD(&clocks.list);
> +
> + ptp_class = class_create(THIS_MODULE, "ptp");
> + if (!ptp_class) {
> + printk(KERN_ERR "ptp: failed to allocate class\n");
> + return -ENOMEM;
> + }
> + ptp_class->dev_attrs = ptp_attrs;
> +
> + err = alloc_chrdev_region(&ptp_devt, 0, PTP_MAX_CLOCKS, "ptp");
> + if (err < 0) {
> + printk(KERN_ERR "ptp: failed to allocate char device region\n");
> + goto no_region;
> + }
> +
> + pr_info("PTP clock support registered\n");
> + return 0;
> +
> +no_region:
> + class_destroy(ptp_class);
> + return err;
> +}
> +
> +subsys_initcall(ptp_init);
> +module_exit(ptp_exit);
> +
> +MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
> +MODULE_DESCRIPTION("PTP clocks support");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/Kbuild b/include/linux/Kbuild
> index 2fc8e14..9959fe4 100644
> --- a/include/linux/Kbuild
> +++ b/include/linux/Kbuild
> @@ -140,6 +140,7 @@ header-y += pkt_sched.h
> header-y += posix_types.h
> header-y += ppdev.h
> header-y += prctl.h
> +header-y += ptp_clock.h
> header-y += qnxtypes.h
> header-y += qnx4_fs.h
> header-y += radeonfb.h
> diff --git a/include/linux/ptp_clock.h b/include/linux/ptp_clock.h
> new file mode 100644
> index 0000000..5a509c5
> --- /dev/null
> +++ b/include/linux/ptp_clock.h
> @@ -0,0 +1,79 @@
> +/*
> + * PTP 1588 clock support - user space interface
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 _PTP_CLOCK_H_
> +#define _PTP_CLOCK_H_
> +
> +#include <linux/ioctl.h>
> +#include <linux/types.h>
> +
> +#define PTP_ENABLE_FEATURE (1<<0)
> +#define PTP_RISING_EDGE (1<<1)
> +#define PTP_FALLING_EDGE (1<<2)
> +
> +enum ptp_request_types {
> + PTP_REQUEST_EXTTS,
> + PTP_REQUEST_PEROUT,
> + PTP_REQUEST_PPS,
> +};
> +
> +struct ptp_clock_caps {
> + __s32 max_adj; /* Maximum frequency adjustment, parts per billon. */
> + int n_alarm; /* Number of programmable alarms. */
> + int n_ext_ts; /* Number of external time stamp channels. */
> + int n_per_out; /* Number of programmable periodic signals. */
> + int pps; /* Whether the clock supports a PPS callback. */
> +};
> +
> +struct ptp_clock_timer {
> + int alarm_index; /* Which alarm to query or configure. */
> + int signum; /* Requested signal. */
> + int flags; /* Zero or TIMER_ABSTIME, see TIMER_SETTIME(2) */
> + struct itimerspec tsp; /* See TIMER_SETTIME(2) */
> +};
> +
> +struct ptp_clock_request {
> + int type; /* One of the ptp_request_types enumeration values. */
> + int index; /* Which channel to configure. */
> + struct timespec ts; /* For period signals, the desired period. */
> + int flags; /* Bit field for PTP_ENABLE_FEATURE or other flags. */
> +};
> +
> +struct ptp_extts_event {
> + int index;
> + struct timespec ts;
> +};
> +
> +#define PTP_CLOCK_VERSION 0x00000001
> +
> +#define PTP_CLK_MAGIC '='
> +
> +#define PTP_CLOCK_APIVERS _IOR (PTP_CLK_MAGIC, 1, __u32)
> +#define PTP_CLOCK_ADJFREQ _IO (PTP_CLK_MAGIC, 2)
> +#define PTP_CLOCK_ADJTIME _IOW (PTP_CLK_MAGIC, 3, struct timespec)
> +#define PTP_CLOCK_GETTIME _IOR (PTP_CLK_MAGIC, 4, struct timespec)
> +#define PTP_CLOCK_SETTIME _IOW (PTP_CLK_MAGIC, 5, struct timespec)
> +
> +#define PTP_CLOCK_GETCAPS _IOR (PTP_CLK_MAGIC, 6, struct ptp_clock_caps)
> +#define PTP_CLOCK_GETTIMER _IOWR (PTP_CLK_MAGIC, 7, struct ptp_clock_timer)
> +#define PTP_CLOCK_SETTIMER _IOW (PTP_CLK_MAGIC, 8, struct ptp_clock_timer)
> +#define PTP_FEATURE_REQUEST _IOW (PTP_CLK_MAGIC, 9, struct ptp_clock_request)
> +
> +#endif
> diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
> new file mode 100644
> index 0000000..d6cc158
> --- /dev/null
> +++ b/include/linux/ptp_clock_kernel.h
> @@ -0,0 +1,137 @@
> +/*
> + * PTP 1588 clock support
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 _PTP_CLOCK_KERNEL_H_
> +#define _PTP_CLOCK_KERNEL_H_
> +
> +#include <linux/ptp_clock.h>
> +
> +/**
> + * struct ptp_clock_info - decribes a PTP hardware clock
> + *
> + * @owner: The clock driver should set to THIS_MODULE.
> + * @name: A short name to identify the clock.
> + * @max_adj: The maximum possible frequency adjustment, in parts per billon.
> + * @n_alarm: The number of programmable alarms.
> + * @n_ext_ts: The number of external time stamp channels.
> + * @n_per_out: The number of programmable periodic signals.
> + * @pps: Indicates whether the clock supports a PPS callback.
> + * @priv: Passed to the clock operations, for the driver's private use.
> + *
> + * clock operations
> + *
> + * @adjfreq: Adjusts the frequency of the hardware clock.
> + * parameter delta: Desired period change in parts per billion.
> + *
> + * @adjtime: Shifts the time of the hardware clock.
> + * parameter ts: Desired change in seconds and nanoseconds.
> + *
> + * @gettime: Reads the current time from the hardware clock.
> + * parameter ts: Holds the result.
> + *
> + * @settime: Set the current time on the hardware clock.
> + * parameter ts: Time value to set.
> + *
> + * @gettimer: Reads the time remaining from the given timer.
> + * parameter index: Which alarm to query.
> + * parameter ts: Holds the result.
> + *
> + * @settimer: Arms the given timer for periodic or one shot operation.
> + * parameter index: Which alarm to set.
> + * parameter abs: TIMER_ABSTIME, or zero for relative timer.
> + * parameter ts: Alarm time and period to set.
> + *
> + * @enable: Request driver to enable or disable an ancillary feature.
> + * parameter request: Desired resource to enable or disable.
> + * parameter on: Caller passes one to enable or zero to disable.
> + *
> + * The callbacks must all return zero on success, non-zero otherwise.
> + */
> +
> +struct ptp_clock_info {
> + struct module *owner;
> + char name[16];
> + s32 max_adj;
> + int n_alarm;
> + int n_ext_ts;
> + int n_per_out;
> + int pps;
> + void *priv;
> + int (*adjfreq)(void *priv, s32 delta);
> + int (*adjtime)(void *priv, struct timespec *ts);
> + int (*gettime)(void *priv, struct timespec *ts);
> + int (*settime)(void *priv, struct timespec *ts);
> + int (*gettimer)(void *priv, int index, struct itimerspec *ts);
> + int (*settimer)(void *priv, int index, int abs, struct itimerspec *ts);
> + int (*enable)(void *priv, struct ptp_clock_request *request, int on);
> +};
> +
> +struct ptp_clock;
> +
> +/**
> + * ptp_clock_register() - register a PTP hardware clock driver
> + *
> + * @info: Structure describing the new clock.
> + */
> +
> +extern struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info);
> +
> +/**
> + * ptp_clock_unregister() - unregister a PTP hardware clock driver
> + *
> + * @ptp: The clock to remove from service.
> + */
> +
> +extern int ptp_clock_unregister(struct ptp_clock *ptp);
> +
> +
> +enum ptp_clock_events {
> + PTP_CLOCK_ALARM,
> + PTP_CLOCK_EXTTS,
> + PTP_CLOCK_PPS,
> +};
> +
> +/**
> + * struct ptp_clock_event - decribes a PTP hardware clock event
> + *
> + * @type: One of the ptp_clock_events enumeration values.
> + * @index: Identifies the source of the event.
> + * @timestamp: When the event occured.
> + */
> +
> +struct ptp_clock_event {
> + int type;
> + int index;
> + u64 timestamp;
> +};
> +
> +/**
> + * ptp_clock_event() - notify the PTP layer about an event
> + *
> + * This function should only be called from interrupt context.
> + *
> + * @ptp: The clock obtained from ptp_clock_register().
> + * @event: Message structure describing the event.
> + */
> +
> +extern void ptp_clock_event(struct ptp_clock *ptp,
> + struct ptp_clock_event *event);
> +
> +#endif
> --
> 1.6.3.3
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [PATCH 05/12] phylib: Allow reading and writing a mii bus from atomic context.
From: Richard Cochran @ 2010-06-15 17:08 UTC (permalink / raw)
To: Grant Likely
Cc: netdev, devicetree-discuss, linuxppc-dev, linux-arm-kernel,
Krzysztof Halasa, Thomas Gleixner
In-Reply-To: <AANLkTikeae2t4KmnawOCo2LqfSI57BKjNnUayY09DfEC@mail.gmail.com>
On Tue, Jun 15, 2010 at 10:43:08AM -0600, Grant Likely wrote:
> On Tue, Jun 15, 2010 at 10:08 AM, Richard Cochran
> <richardcochran@gmail.com> wrote:
> > In order to support hardware time stamping from a PHY, it is necessary to
> > read from the PHY while running in_interrupt(). This patch allows a mii
> > bus to operate in an atomic context. An mii_bus driver may declare itself
> > capable for this mode. Drivers which do not do this will remain with the
> > default that bus operations may sleep.
> >
> > Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
>
> Last I checked, the MDIO bus is very slow. Is this really a good
> idea? How much latency does MDIO access have on the hardware you are
> working with?
Yes, MDIO access is slow, and it can vary (eg bit banging
implementations). It clear that getting PHY timestamps is costly, but
for applications that want PTP synchronization, one is willing to pay
the price.
> I also don't like the idea of taking a spin lock during MDIO
> operations, and the dual locking mode in the core code.
Originally, the phylib used a spinlock for this. It was replaced with
a mutex in 35b5f6b1a82b5c586e0b24c711dc6ba944e88ef1 in order to
accommodate mdio busses that may need to sleep. So, keeping the option
to use a spinlock is similar to the previous implementation.
> I'd rather see separate atomic context hooks that doesn't obtain the
> mutex under the assumption that the caller has already done so. If
> the bus doesn't support atomic access, then it won't implement the
> hooks and the core code should return an error.
>
> I've cc'd Thomas Gleixner. He might have a better idea about how to
> implement what you're trying to do.
Okay, I'm open to suggestions...
Thanks,
Richard
^ permalink raw reply
* Re: [PATCH 10/12] ptp: Added a clock that uses the eTSEC found on the MPC85xx.
From: Grant Likely @ 2010-06-15 17:20 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, devicetree-discuss, linuxppc-dev, linux-arm-kernel,
Krzysztof Halasa
In-Reply-To: <041faf3cb6ecbedf8f3ff2104914f91d2a24fc4d.1276615626.git.richard.cochran@omicron.at>
On Tue, Jun 15, 2010 at 10:10 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> The eTSEC includes a PTP clock with quite a few features. This patch adds
> support for the basic clock adjustment functions, plus two external time
> stamps and one alarm.
>
> Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
Hi Richard,
comments below...
> ---
> Documentation/powerpc/dts-bindings/fsl/tsec.txt | 54 +++
> arch/powerpc/boot/dts/mpc8313erdb.dts | 13 +
> arch/powerpc/boot/dts/p2020ds.dts | 13 +
> arch/powerpc/boot/dts/p2020rdb.dts | 13 +
> drivers/net/Makefile | 1 +
> drivers/net/gianfar_ptp.c | 518 +++++++++++++++++++++++
> drivers/net/gianfar_ptp_reg.h | 113 +++++
Is this header file used by anything other than gianfar_ptp.c? If
not, then roll the two files together.
> drivers/ptp/Kconfig | 13 +
> 8 files changed, 738 insertions(+), 0 deletions(-)
> create mode 100644 drivers/net/gianfar_ptp.c
> create mode 100644 drivers/net/gianfar_ptp_reg.h
>
> diff --git a/Documentation/powerpc/dts-bindings/fsl/tsec.txt b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
> index edb7ae1..14577e9 100644
> --- a/Documentation/powerpc/dts-bindings/fsl/tsec.txt
> +++ b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
> @@ -74,3 +74,57 @@ Example:
> interrupt-parent = <&mpic>;
> phy-handle = <&phy0>
> };
> +
> +* Gianfar PTP clock nodes
> +
> +General Properties:
> +
> + - compatible Should be "fsl,etsec-ptp"
> + - reg Offset and length of the register set for the device
> + - interrupts There should be at least two interrupts. Some devices
> + have as many as four PTP related interrupts.
> +
> +Clock Properties:
> +
> + - tclk_period Timer reference clock period in nanoseconds.
> + - tmr_prsc Prescaler, divides the output clock.
> + - tmr_add Frequency compensation value.
Use dash ('-') not underscore ('_') in property names.
> + - cksel 0= external clock, 1= eTSEC system clock, 3= RTC clock input.
> + Currently the driver only supports choice "1".
If you encode this value as a string, then it will be friendly for humans too.
> + - tmr_fiper1 Fixed interval period pulse generator.
> + - tmr_fiper2 Fixed interval period pulse generator.
I could use more explication here. Is this a divider value?
Computers are good at making calculations, and the driver can obtain
the clock frequency supplied to the device. It may be more useful to
specify here the desired frequency rather than the divider. Certainly
more human-friendly too.
> +
> + These properties set the operational parameters for the PTP
> + clock. You must choose these carefully for the clock to work right.
> + Here is how to figure good values:
> +
> + TimerOsc = system clock MHz
> + tclk_period = desired clock period nanoseconds
> + NominalFreq = 1000 / tclk_period MHz
> + FreqDivRatio = TimerOsc / NominalFreq (must be greater that 1.0)
> + tmr_add = ceil(2^32 / FreqDivRatio)
> + OutputClock = NominalFreq / tmr_prsc MHz
> + PulseWidth = 1 / OutputClock microseconds
> + FiperFreq1 = desired frequency in Hz
> + FiperDiv1 = 1000000 * OutputClock / FiperFreq1
> + tmr_fiper1 = tmr_prsc * tclk_period * FiperDiv1 - tclk_period
> +
> + The calculation for tmr_fiper2 is the same as for tmr_fiper1. The
> + driver expects that tmr_fiper1 will be correctly set to produce a 1
> + Pulse Per Second (PPS) signal, since this will be offered to the PPS
> + subsystem to synchronize the Linux clock.
> +
> +Example:
> +
> + ptp_clock@24E00 {
> + compatible = "fsl,etsec-ptp";
> + reg = <0x24E00 0xB0>;
> + interrupts = <12 0x8 13 0x8>;
> + interrupt-parent = < &ipic >;
> + tclk_period = <10>;
> + tmr_prsc = <100>;
> + tmr_add = <0x999999A4>;
> + cksel = <0x1>;
> + tmr_fiper1 = <0x3B9AC9F6>;
> + tmr_fiper2 = <0x00018696>;
> + };
> diff --git a/arch/powerpc/boot/dts/mpc8313erdb.dts b/arch/powerpc/boot/dts/mpc8313erdb.dts
> index 183f2aa..0526384 100644
> --- a/arch/powerpc/boot/dts/mpc8313erdb.dts
> +++ b/arch/powerpc/boot/dts/mpc8313erdb.dts
> @@ -208,6 +208,19 @@
> sleep = <&pmc 0x00300000>;
> };
>
> + ptp_clock@24E00 {
> + compatible = "fsl,etsec-ptp";
> + reg = <0x24E00 0xB0>;
> + interrupts = <12 0x8 13 0x8>;
> + interrupt-parent = < &ipic >;
> + tclk_period = <10>;
> + tmr_prsc = <100>;
> + tmr_add = <0x999999A4>;
> + cksel = <0x1>;
> + tmr_fiper1 = <0x3B9AC9F6>;
> + tmr_fiper2 = <0x00018696>;
> + };
> +
> enet0: ethernet@24000 {
> #address-cells = <1>;
> #size-cells = <1>;
> diff --git a/arch/powerpc/boot/dts/p2020ds.dts b/arch/powerpc/boot/dts/p2020ds.dts
> index 1101914..ae5dc4d 100644
> --- a/arch/powerpc/boot/dts/p2020ds.dts
> +++ b/arch/powerpc/boot/dts/p2020ds.dts
> @@ -336,6 +336,19 @@
> phy_type = "ulpi";
> };
>
> + ptp_clock@24E00 {
> + compatible = "fsl,etsec-ptp";
> + reg = <0x24E00 0xB0>;
> + interrupts = <68 2 69 2 70 2>;
> + interrupt-parent = < &mpic >;
> + tclk_period = <5>;
> + tmr_prsc = <200>;
> + tmr_add = <0xCCCCCCCD>;
> + cksel = <1>;
> + tmr_fiper1 = <0x3B9AC9FB>;
> + tmr_fiper2 = <0x0001869B>;
> + };
> +
> enet0: ethernet@24000 {
> #address-cells = <1>;
> #size-cells = <1>;
> diff --git a/arch/powerpc/boot/dts/p2020rdb.dts b/arch/powerpc/boot/dts/p2020rdb.dts
> index da4cb0d..133945c 100644
> --- a/arch/powerpc/boot/dts/p2020rdb.dts
> +++ b/arch/powerpc/boot/dts/p2020rdb.dts
> @@ -396,6 +396,19 @@
> phy_type = "ulpi";
> };
>
> + ptp_clock@24E00 {
> + compatible = "fsl,etsec-ptp";
> + reg = <0x24E00 0xB0>;
> + interrupts = <68 2 69 2 70 2>;
> + interrupt-parent = < &mpic >;
> + tclk_period = <5>;
> + tmr_prsc = <200>;
> + tmr_add = <0xCCCCCCCD>;
> + cksel = <1>;
> + tmr_fiper1 = <0x3B9AC9FB>;
> + tmr_fiper2 = <0x0001869B>;
> + };
> +
> enet0: ethernet@24000 {
> #address-cells = <1>;
> #size-cells = <1>;
> diff --git a/drivers/net/Makefile b/drivers/net/Makefile
> index 0a0512a..389c0d9 100644
> --- a/drivers/net/Makefile
> +++ b/drivers/net/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_ATL2) += atlx/
> obj-$(CONFIG_ATL1E) += atl1e/
> obj-$(CONFIG_ATL1C) += atl1c/
> obj-$(CONFIG_GIANFAR) += gianfar_driver.o
> +obj-$(CONFIG_PTP_1588_CLOCK_GIANFAR) += gianfar_ptp.o
> obj-$(CONFIG_TEHUTI) += tehuti.o
> obj-$(CONFIG_ENIC) += enic/
> obj-$(CONFIG_JME) += jme.o
> diff --git a/drivers/net/gianfar_ptp.c b/drivers/net/gianfar_ptp.c
> new file mode 100644
> index 0000000..0991652
> --- /dev/null
> +++ b/drivers/net/gianfar_ptp.c
> @@ -0,0 +1,518 @@
> +/*
> + * PTP 1588 clock using the eTSEC
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 <linux/device.h>
> +#include <linux/hrtimer.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/timex.h>
> +#include <linux/io.h>
> +
> +#include <linux/ptp_clock_kernel.h>
> +
> +#include "gianfar_ptp_reg.h"
> +#include "gianfar.h"
> +
> +#define DRIVER "gianfar_ptp"
> +#define N_ALARM 1 /* first alarm is used internally to reset fipers */
> +#define N_EXT_TS 2
> +#define REG_SIZE sizeof(struct gianfar_ptp_registers)
> +
> +struct etsects {
> + struct gianfar_ptp_registers *regs;
> + struct ptp_clock *clock;
> + int irq;
> + u64 alarm_interval; /* for periodic alarm */
> + u64 alarm_value;
> + u32 tclk_period; /* nanoseconds */
> + u32 tmr_prsc;
> + u32 tmr_add;
> + u32 cksel;
> + u32 tmr_fiper1;
> + u32 tmr_fiper2;
> +};
> +
> +/* Private globals */
> +static struct etsects the_clock;
Will there ever be multiple instances of this device?
> +DEFINE_SPINLOCK(register_lock);
> +
> +/*
> + * Register access functions
> + */
> +
> +static u64 tmr_cnt_read(struct etsects *etsects)
> +{
> + u64 ns;
> + u32 lo, hi;
> +
> + lo = gfar_read(&etsects->regs->tmr_cnt_l);
> + hi = gfar_read(&etsects->regs->tmr_cnt_h);
> + ns = ((u64) hi) << 32;
> + ns |= lo;
> + return ns;
> +}
> +
> +static void tmr_cnt_write(struct etsects *etsects, u64 ns)
> +{
> + u32 hi = ns >> 32;
> + u32 lo = ns & 0xffffffff;
> +
> + gfar_write(&etsects->regs->tmr_cnt_l, lo);
> + gfar_write(&etsects->regs->tmr_cnt_h, hi);
> +}
> +
> +static void set_alarm(struct etsects *etsects)
> +{
> + u64 ns;
> + u32 lo, hi;
> +
> + ns = tmr_cnt_read(etsects) + 1500000000ULL;
> + ns = div_u64(ns, 1000000000UL) * 1000000000ULL;
> + ns -= etsects->tclk_period;
> + hi = ns >> 32;
> + lo = ns & 0xffffffff;
> + gfar_write(&etsects->regs->tmr_alarm1_l, lo);
> + gfar_write(&etsects->regs->tmr_alarm1_h, hi);
> +}
> +
> +static void set_fipers(struct etsects *etsects)
> +{
> + u32 tmr_ctrl = gfar_read(&etsects->regs->tmr_ctrl);
> +
> + gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl & (~TE));
> + gfar_write(&etsects->regs->tmr_prsc, etsects->tmr_prsc);
> + gfar_write(&etsects->regs->tmr_fiper1, etsects->tmr_fiper1);
> + gfar_write(&etsects->regs->tmr_fiper2, etsects->tmr_fiper2);
> + set_alarm(etsects);
> + gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl|TE);
> +}
> +
> +/*
> + * Interrupt service routine
> + */
> +
> +static irqreturn_t isr(int irq, void *priv)
> +{
> + struct etsects *etsects = priv;
> + struct ptp_clock_event event;
> + u64 ns;
> + u32 ack = 0, lo, hi, mask, val;
> +
> + val = gfar_read(&etsects->regs->tmr_tevent);
> +
> + if (val & ETS1) {
> + ack |= ETS1;
> + hi = gfar_read(&etsects->regs->tmr_etts1_h);
> + lo = gfar_read(&etsects->regs->tmr_etts1_l);
> + event.type = PTP_CLOCK_EXTTS;
> + event.index = 0;
> + event.timestamp = ((u64) hi) << 32;
> + event.timestamp |= lo;
> + ptp_clock_event(etsects->clock, &event);
> + }
> +
> + if (val & ETS2) {
> + ack |= ETS2;
> + hi = gfar_read(&etsects->regs->tmr_etts2_h);
> + lo = gfar_read(&etsects->regs->tmr_etts2_l);
> + event.type = PTP_CLOCK_EXTTS;
> + event.index = 1;
> + event.timestamp = ((u64) hi) << 32;
> + event.timestamp |= lo;
> + ptp_clock_event(etsects->clock, &event);
> + }
> +
> + if (val & ALM2) {
> + ack |= ALM2;
> + if (etsects->alarm_value) {
> + event.type = PTP_CLOCK_ALARM;
> + event.index = 0;
> + event.timestamp = etsects->alarm_value;
> + ptp_clock_event(etsects->clock, &event);
> + }
> + if (etsects->alarm_interval) {
> + ns = etsects->alarm_value + etsects->alarm_interval;
> + hi = ns >> 32;
> + lo = ns & 0xffffffff;
> + spin_lock(®ister_lock);
> + gfar_write(&etsects->regs->tmr_alarm2_l, lo);
> + gfar_write(&etsects->regs->tmr_alarm2_h, hi);
> + spin_unlock(®ister_lock);
> + etsects->alarm_value = ns;
> + } else {
> + gfar_write(&etsects->regs->tmr_tevent, ALM2);
> + spin_lock(®ister_lock);
> + mask = gfar_read(&etsects->regs->tmr_temask);
> + mask &= ~ALM2EN;
> + gfar_write(&etsects->regs->tmr_temask, mask);
> + spin_unlock(®ister_lock);
> + etsects->alarm_value = 0;
> + etsects->alarm_interval = 0;
> + }
> + }
> +
> + if (ack) {
> + gfar_write(&etsects->regs->tmr_tevent, ack);
> + return IRQ_HANDLED;
> + } else
> + return IRQ_NONE;
> +}
> +
> +/*
> + * PTP clock operations
> + */
> +
> +static int ptp_gianfar_adjfreq(void *priv, s32 ppb)
> +{
> + u64 adj;
> + u32 diff, tmr_add;
> + int neg_adj = 0;
> + struct etsects *etsects = priv;
> +
> + if (!ppb)
> + return 0;
> +
> + if (ppb < 0) {
> + neg_adj = 1;
> + ppb = -ppb;
> + }
> + tmr_add = etsects->tmr_add;
> + adj = tmr_add;
> + adj *= ppb;
> + diff = div_u64(adj, 1000000000ULL);
> +
> + tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff;
> +
> + gfar_write(&etsects->regs->tmr_add, tmr_add);
> +
> + return 0;
> +}
> +
> +static int ptp_gianfar_adjtime(void *priv, struct timespec *ts)
> +{
> + s64 delta, now;
> + unsigned long flags;
> + struct etsects *etsects = priv;
> +
> + delta = 1000000000LL * ts->tv_sec;
> + delta += ts->tv_nsec;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + now = tmr_cnt_read(etsects);
> + now += delta;
> + tmr_cnt_write(etsects, now);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + set_fipers(etsects);
> +
> + return 0;
> +}
> +
> +static int ptp_gianfar_gettime(void *priv, struct timespec *ts)
> +{
> + u64 ns;
> + u32 remainder;
> + unsigned long flags;
> + struct etsects *etsects = priv;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + ns = tmr_cnt_read(etsects);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
> + ts->tv_nsec = remainder;
> + return 0;
> +}
> +
> +static int ptp_gianfar_settime(void *priv, struct timespec *ts)
> +{
> + u64 ns;
> + unsigned long flags;
> + struct etsects *etsects = priv;
> +
> + ns = ts->tv_sec * 1000000000ULL;
> + ns += ts->tv_nsec;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + tmr_cnt_write(etsects, ns);
> + set_fipers(etsects);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + return 0;
> +}
> +
> +static int ptp_gianfar_gettimer(void *priv, int index, struct itimerspec *ts)
> +{
> + u64 now, ns;
> + u32 remainder;
> + unsigned long flags;
> + struct etsects *etsects = priv;
> +
> + ns = etsects->alarm_interval;
> +
> + ts->it_interval.tv_sec = div_u64_rem(ns, 1000000000, &remainder);
> + ts->it_interval.tv_nsec = remainder;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> + now = tmr_cnt_read(etsects);
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + ns = etsects->alarm_value - now;
> +
> + ts->it_value.tv_sec = div_u64_rem(ns, 1000000000, &remainder);
> + ts->it_value.tv_nsec = remainder;
> +
> + return 0;
> +}
> +
> +static int ptp_gianfar_settimer(void *p, int i, int abs, struct itimerspec *ts)
> +{
> + u64 ns;
> + u32 lo, hi, mask;
> + unsigned long flags;
> + struct etsects *etsects = p;
> +
> + ns = ts->it_interval.tv_sec * 1000000000ULL;
> + ns += ts->it_interval.tv_nsec;
> +
> + etsects->alarm_interval = ns;
> +
> + ns = ts->it_value.tv_sec * 1000000000ULL;
> + ns += ts->it_value.tv_nsec;
> +
> + if (!ns) {
> + /* Cancel the timer. */
> + etsects->alarm_value = 0;
> + etsects->alarm_interval = 0;
> + return 0;
> + }
> +
> + if (!abs) {
> + spin_lock_irqsave(®ister_lock, flags);
> + ns += tmr_cnt_read(etsects);
> + spin_unlock_irqrestore(®ister_lock, flags);
> + }
> +
> + etsects->alarm_value = ns;
> +
> + hi = ns >> 32;
> + lo = ns & 0xffffffff;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + gfar_write(&etsects->regs->tmr_alarm2_l, lo);
> + gfar_write(&etsects->regs->tmr_alarm2_h, hi);
> +
> + mask = gfar_read(&etsects->regs->tmr_temask);
> + mask |= ALM2EN;
> + gfar_write(&etsects->regs->tmr_temask, mask);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + return 0;
> +}
> +
> +static int ptp_gianfar_enable(void *priv, struct ptp_clock_request *rq, int on)
> +{
> + struct etsects *etsects = priv;
> + unsigned long flags;
> + u32 bit, mask;
> +
> + switch (rq->type) {
> + case PTP_REQUEST_EXTTS:
> + switch (rq->index) {
> + case 0:
> + bit = ETS1EN;
> + break;
> + case 1:
> + bit = ETS2EN;
> + break;
> + default:
> + return -EINVAL;
> + }
> + spin_lock_irqsave(®ister_lock, flags);
> + if (on) {
> + mask = gfar_read(&etsects->regs->tmr_temask);
> + mask |= bit;
> + gfar_write(&etsects->regs->tmr_temask, mask);
> + } else {
> + mask = gfar_read(&etsects->regs->tmr_temask);
> + mask &= ~bit;
> + gfar_write(&etsects->regs->tmr_temask, mask);
> + }
> + spin_unlock_irqrestore(®ister_lock, flags);
> + return 0;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static struct ptp_clock_info ptp_gianfar_caps = {
> + .owner = THIS_MODULE,
> + .name = "gianfar clock",
> + .max_adj = 512000,
> + .n_alarm = N_ALARM,
> + .n_ext_ts = N_EXT_TS,
> + .n_per_out = 0,
> + .pps = 0,
> + .priv = &the_clock,
> + .adjfreq = ptp_gianfar_adjfreq,
> + .adjtime = ptp_gianfar_adjtime,
> + .gettime = ptp_gianfar_gettime,
> + .settime = ptp_gianfar_settime,
> + .gettimer = ptp_gianfar_gettimer,
> + .settimer = ptp_gianfar_settimer,
> + .enable = ptp_gianfar_enable,
> +};
> +
> +/* OF device tree */
> +
> +static int get_of_u32(struct device_node *node, char *str, u32 *val)
> +{
> + int plen;
> + const u32 *prop = of_get_property(node, str, &plen);
> +
> + if (!prop || plen != sizeof(*prop))
> + return -1;
> + *val = *prop;
> + return 0;
> +}
> +
> +static int gianfar_ptp_probe(struct of_device *dev,
> + const struct of_device_id *match)
> +{
> + u64 addr, size;
> + struct device_node *node = dev->dev.of_node;
> + struct etsects *etsects = &the_clock;
> + struct timespec now;
> + phys_addr_t reg_addr;
> + unsigned long reg_size;
> + u32 tmr_ctrl;
> +
> + if (get_of_u32(node, "tclk_period", &etsects->tclk_period) ||
> + get_of_u32(node, "tmr_prsc", &etsects->tmr_prsc) ||
> + get_of_u32(node, "tmr_add", &etsects->tmr_add) ||
> + get_of_u32(node, "cksel", &etsects->cksel) ||
> + get_of_u32(node, "tmr_fiper1", &etsects->tmr_fiper1) ||
> + get_of_u32(node, "tmr_fiper2", &etsects->tmr_fiper2)) {
> + pr_err("device tree node missing required elements\n");
> + return -ENODEV;
> + }
> +
> + etsects->irq = irq_of_parse_and_map(node, 0);
> +
> + if (etsects->irq == NO_IRQ) {
> + pr_err("irq not in device tree\n");
> + return -ENODEV;
> + }
> + if (request_irq(etsects->irq, isr, 0, DRIVER, etsects)) {
> + pr_err("request_irq failed\n");
> + return -ENODEV;
> + }
> +
> + addr = of_translate_address(node, of_get_address(node, 0, &size, NULL));
> + reg_addr = addr;
> + reg_size = size;
> + if (reg_size < REG_SIZE) {
> + pr_warning("device tree reg range %lu too small\n", reg_size);
> + reg_size = REG_SIZE;
> + }
> + etsects->regs = ioremap(reg_addr, reg_size);
> + if (!etsects->regs) {
> + pr_err("ioremap ptp registers failed\n");
> + return -EINVAL;
> + }
Consider of_iomap(), it will simplify the code a bit.
> +
> + tmr_ctrl =
> + (etsects->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT |
> + (etsects->cksel & CKSEL_MASK) << CKSEL_SHIFT;
> +
> + getnstimeofday(&now);
> + ptp_gianfar_settime(etsects, &now);
> +
> + gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl);
> + gfar_write(&etsects->regs->tmr_add, etsects->tmr_add);
> + gfar_write(&etsects->regs->tmr_prsc, etsects->tmr_prsc);
> + gfar_write(&etsects->regs->tmr_fiper1, etsects->tmr_fiper1);
> + gfar_write(&etsects->regs->tmr_fiper2, etsects->tmr_fiper2);
> + set_alarm(etsects);
> + gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl|FS|RTPE|TE);
> +
> + etsects->clock = ptp_clock_register(&ptp_gianfar_caps);
> +
> + return IS_ERR(etsects->clock) ? PTR_ERR(etsects->clock) : 0;
> +}
> +
> +static int gianfar_ptp_remove(struct of_device *dev)
> +{
> + gfar_write(&the_clock.regs->tmr_temask, 0);
> + gfar_write(&the_clock.regs->tmr_ctrl, 0);
> +
> + ptp_clock_unregister(the_clock.clock);
> +
> + free_irq(the_clock.irq, &the_clock);
> +
> + iounmap(the_clock.regs);
> +
> + return 0;
> +}
> +
> +static struct of_device_id match_table[] = {
> + { .compatible = "fsl,etsec-ptp" },
> + {},
> +};
> +
> +static struct of_platform_driver gianfar_ptp_driver = {
> + .driver = {
> + .name = "gianfar_ptp",
> + .of_match_table = match_table,
> + .owner = THIS_MODULE,
> + },
> + .probe = gianfar_ptp_probe,
> + .remove = gianfar_ptp_remove,
> +};
> +
> +/* module operations */
> +
> +static void __exit ptp_gianfar_exit(void)
> +{
> + of_unregister_platform_driver(&gianfar_ptp_driver);
> +}
> +
> +static int __init ptp_gianfar_init(void)
> +{
> + return of_register_platform_driver(&gianfar_ptp_driver);
> +}
> +
> +module_init(ptp_gianfar_init);
Move ptp_gianfar_exit() definition here so it is immediately before
the module_exit() line.
> +module_exit(ptp_gianfar_exit);
> +
> +MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
> +MODULE_DESCRIPTION("PTP clock using the eTSEC");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/net/gianfar_ptp_reg.h b/drivers/net/gianfar_ptp_reg.h
> new file mode 100644
> index 0000000..95e171f
> --- /dev/null
> +++ b/drivers/net/gianfar_ptp_reg.h
> @@ -0,0 +1,113 @@
> +/* gianfar_ptp_reg.h
> + * Generated by regen.tcl on Thu May 13 01:38:57 PM CEST 2010
> + *
> + * PTP 1588 clock using the gianfar eTSEC
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 _GIANFAR_PTP_REG_H_
> +#define _GIANFAR_PTP_REG_H_
> +
> +struct gianfar_ptp_registers {
> + u32 tmr_ctrl; /* Timer control register */
> + u32 tmr_tevent; /* Timestamp event register */
> + u32 tmr_temask; /* Timer event mask register */
> + u32 tmr_pevent; /* Timestamp event register */
> + u32 tmr_pemask; /* Timer event mask register */
> + u32 tmr_stat; /* Timestamp status register */
> + u32 tmr_cnt_h; /* Timer counter high register */
> + u32 tmr_cnt_l; /* Timer counter low register */
> + u32 tmr_add; /* Timer drift compensation addend register */
> + u32 tmr_acc; /* Timer accumulator register */
> + u32 tmr_prsc; /* Timer prescale */
> + u8 res1[4];
> + u32 tmroff_h; /* Timer offset high */
> + u32 tmroff_l; /* Timer offset low */
> + u8 res2[8];
> + u32 tmr_alarm1_h; /* Timer alarm 1 high register */
> + u32 tmr_alarm1_l; /* Timer alarm 1 high register */
> + u32 tmr_alarm2_h; /* Timer alarm 2 high register */
> + u32 tmr_alarm2_l; /* Timer alarm 2 high register */
> + u8 res3[48];
> + u32 tmr_fiper1; /* Timer fixed period interval */
> + u32 tmr_fiper2; /* Timer fixed period interval */
> + u32 tmr_fiper3; /* Timer fixed period interval */
> + u8 res4[20];
> + u32 tmr_etts1_h; /* Timestamp of general purpose external trigger */
> + u32 tmr_etts1_l; /* Timestamp of general purpose external trigger */
> + u32 tmr_etts2_h; /* Timestamp of general purpose external trigger */
> + u32 tmr_etts2_l; /* Timestamp of general purpose external trigger */
> +};
> +
> +/* Bit definitions for the TMR_CTRL register */
> +#define ALM1P (1<<31) /* Alarm1 output polarity */
> +#define ALM2P (1<<30) /* Alarm2 output polarity */
> +#define FS (1<<28) /* FIPER start indication */
> +#define PP1L (1<<27) /* Fiper1 pulse loopback mode enabled. */
> +#define PP2L (1<<26) /* Fiper2 pulse loopback mode enabled. */
> +#define TCLK_PERIOD_SHIFT (16) /* 1588 timer reference clock period. */
> +#define TCLK_PERIOD_MASK (0x3ff)
> +#define RTPE (1<<15) /* Record Tx Timestamp to PAL Enable. */
> +#define FRD (1<<14) /* FIPER Realignment Disable */
> +#define ESFDP (1<<11) /* External Tx/Rx SFD Polarity. */
> +#define ESFDE (1<<10) /* External Tx/Rx SFD Enable. */
> +#define ETEP2 (1<<9) /* External trigger 2 edge polarity */
> +#define ETEP1 (1<<8) /* External trigger 1 edge polarity */
> +#define COPH (1<<7) /* Generated clock (TSEC_1588_GCLK) output phase. */
> +#define CIPH (1<<6) /* External oscillator input clock phase. */
> +#define TMSR (1<<5) /* Timer soft reset. When enabled, it resets all the timer registers and state machines. */
> +#define BYP (1<<3) /* Bypass drift compensated clock */
> +#define TE (1<<2) /* 1588 timer enable. If not enabled, all the timer registers and state machines are disabled. */
> +#define CKSEL_SHIFT (0) /* 1588 Timer reference clock source select. */
> +#define CKSEL_MASK (0x3)
> +
> +/* Bit definitions for the TMR_TEVENT register */
> +#define ETS2 (1<<25) /* External trigger 2 timestamp sampled */
> +#define ETS1 (1<<24) /* External trigger 1 timestamp sampled */
> +#define ALM2 (1<<17) /* Current time equaled alarm time register 2 */
> +#define ALM1 (1<<16) /* Current time equaled alarm time register 1 */
> +#define PP1 (1<<7) /* Indicates that a periodic pulse has been generated based on FIPER1 register */
> +#define PP2 (1<<6) /* Indicates that a periodic pulse has been generated based on FIPER2 register */
> +#define PP3 (1<<5) /* Indicates that a periodic pulse has been generated based on FIPER3 register */
> +
> +/* Bit definitions for the TMR_TEMASK register */
> +#define ETS2EN (1<<25) /* External trigger 2 timestamp sample event enable */
> +#define ETS1EN (1<<24) /* External trigger 1 timestamp sample event enable */
> +#define ALM2EN (1<<17) /* Timer ALM2 event enable */
> +#define ALM1EN (1<<16) /* Timer ALM1 event enable */
> +#define PP1EN (1<<7) /* Periodic pulse event 1 enable */
> +#define PP2EN (1<<6) /* Periodic pulse event 2 enable */
> +
> +/* Bit definitions for the TMR_PEVENT register */
> +#define TXP2 (1<<9) /* Indicates that a PTP frame has been transmitted and its timestamp is stored in TXTS2 register */
> +#define TXP1 (1<<8) /* Indicates that a PTP frame has been transmitted and its timestamp is stored in TXTS1 register */
> +#define RXP (1<<0) /* Indicates that a PTP frame has been received */
> +
> +/* Bit definitions for the TMR_PEMASK register */
> +#define TXP2EN (1<<9) /* Transmit PTP packet event 2 enable */
> +#define TXP1EN (1<<8) /* Transmit PTP packet event 1 enable */
> +#define RXPEN (1<<0) /* Receive PTP packet event enable */
> +
> +/* Bit definitions for the TMR_STAT register */
> +#define STAT_VEC_SHIFT (0) /* Timer general purpose status vector */
> +#define STAT_VEC_MASK (0x3f)
> +
> +/* Bit definitions for the TMR_PRSC register */
> +#define PRSC_OCK_SHIFT (0) /* Output clock division/prescale factor. */
> +#define PRSC_OCK_MASK (0xffff)
> +
> +#endif
> diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
> index 9390d44..3b7bd73 100644
> --- a/drivers/ptp/Kconfig
> +++ b/drivers/ptp/Kconfig
> @@ -35,4 +35,17 @@ config PTP_1588_CLOCK_LINUX
> To compile this driver as a module, choose M here: the module
> will be called ptp_linux.
>
> +config PTP_1588_CLOCK_GIANFAR
> + tristate "Freescale eTSEC as PTP clock"
> + depends on PTP_1588_CLOCK
> + depends on GIANFAR
> + help
> + This driver adds support for using the eTSEC as a PTP
> + clock. This clock is only useful if your PTP programs are
> + getting hardware time stamps on the PTP Ethernet packets
> + using the SO_TIMESTAMPING API.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called gianfar_ptp.
> +
> endmenu
> --
> 1.6.3.3
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [v2 Patch 1/2] s2io: add dynamic LRO disable support
From: David Miller @ 2010-06-15 17:21 UTC (permalink / raw)
To: Ramkrishna.Vepa; +Cc: amwang, bhutchings, netdev, nhorman, sgruszka, herbert.xu
In-Reply-To: <FCA91A92EE52B041906A0358FC28FCC38EF15A949F@FRE1EXCH02.hq.exar.com>
From: Ramkrishna Vepa <Ramkrishna.Vepa@exar.com>
Date: Tue, 15 Jun 2010 09:07:47 -0700
> Yes, this will work.
>
> Thanks,
> Ram
>
> The information and any attached documents contained in this message
> may be confidential and/or legally privileged. The message is
> intended solely for the addressee(s). If you are not the intended
> recipient, you are hereby notified that any use, dissemination, or
> reproduction is strictly prohibited and may be unlawful. If you are
> not the intended recipient, please contact the sender immediately by
> return e-mail and destroy all copies of the original message.
Please, your legal notice is 10 times larger than what you're actually
saying.
That is rediculious. Even more rediculious is using such a notice for
a posting to a public mailing list that is published and duplicated
hundreds and hundreds of times to public archive sites and elsewhere.
Your notice has no meaning in this context, so not only is it wasteful
and annoying, it's useless.
Please us a gmail or yahoo email account to post to these mailing lists
if you can't avoid attaching this legal notice to your postings here.
It's wasting bandwidth and people's time unnecessarily.
Thank you.
^ permalink raw reply
* Re: [RFC patch net/next] net: Hoist assigns from if?
From: David Miller @ 2010-06-15 17:23 UTC (permalink / raw)
To: joe; +Cc: eric.dumazet, netdev
In-Reply-To: <1276618932.1586.64.camel@Joe-Laptop.home>
From: Joe Perches <joe@perches.com>
Date: Tue, 15 Jun 2010 09:22:12 -0700
> On Mon, 2010-06-14 at 22:59 -0700, David Miller wrote:
>> But that to me just means that until git has a friendlier way to
>> walk backwards through blame information for a line to skip past
>> the cleanup crapola, we shouldn't make a lot of these kinds of
>> changes.
>
> What do you think is missing from the gitk file viewer?
I don't use the gitk file viewer, I use "git blame".
Requiring a GUI to get at the information I need is unacceptable. I
often work remotely and use text consoles for everything, even email
and web browsing.
^ permalink raw reply
* Re: [RFC patch net/next] net: Hoist assigns from if?
From: Joe Perches @ 2010-06-15 17:42 UTC (permalink / raw)
To: David Miller; +Cc: eric.dumazet, netdev
In-Reply-To: <20100615.102305.28825768.davem@davemloft.net>
On Tue, 2010-06-15 at 10:23 -0700, David Miller wrote:
> > What do you think is missing from the gitk file viewer?
> I don't use the gitk file viewer, I use "git blame".
I've never thought git blame was very good for changelog
tracking as it's a static representation at a particular
commit/rev.
> Requiring a GUI to get at the information I need is unacceptable. I
> often work remotely and use text consoles for everything, even email
> and web browsing.
You want an interactive, text-based (curses?) git-blame style
file viewer with push/pop git rev-list capabilities?
Something akin to emacs egit?
^ permalink raw reply
* Re: [RFC patch net/next] net: Hoist assigns from if?
From: David Miller @ 2010-06-15 17:46 UTC (permalink / raw)
To: joe; +Cc: eric.dumazet, netdev
In-Reply-To: <1276623750.1586.81.camel@Joe-Laptop.home>
From: Joe Perches <joe@perches.com>
Date: Tue, 15 Jun 2010 10:42:30 -0700
> On Tue, 2010-06-15 at 10:23 -0700, David Miller wrote:
>> > What do you think is missing from the gitk file viewer?
>> I don't use the gitk file viewer, I use "git blame".
>
> I've never thought git blame was very good for changelog
> tracking as it's a static representation at a particular
> commit/rev.
>
>> Requiring a GUI to get at the information I need is unacceptable. I
>> often work remotely and use text consoles for everything, even email
>> and web browsing.
>
> You want an interactive, text-based (curses?) git-blame style
> file viewer with push/pop git rev-list capabilities?
>
> Something akin to emacs egit?
No, I want "git blame" to tell me where a line of code came from,
rather than who the last turkey was who did some coding style
cleanup.
I don't want something interactive, I want a command line I can
type in which gives me the answer. "git blame" on the surface
can do this, and most of the time it serves my needs, but as soon
as rabid cleanups enter into the picture, the tool becomes useless.
^ permalink raw reply
* Re: [RFC patch net/next] net: Hoist assigns from if?
From: Joe Perches @ 2010-06-15 17:51 UTC (permalink / raw)
To: David Miller; +Cc: eric.dumazet, netdev
In-Reply-To: <20100615.104625.179919135.davem@davemloft.net>
On Tue, 2010-06-15 at 10:46 -0700, David Miller wrote:
> I want a command line I can
> type in which gives me the answer. "git blame" on the surface
> can do this, and most of the time it serves my needs, but as soon
> as rabid cleanups enter into the picture, the tool becomes useless.
akin to git blame --L <range> --history?
^ permalink raw reply
* Re: [RFC patch net/next] net: Hoist assigns from if?
From: Eric Dumazet @ 2010-06-15 18:04 UTC (permalink / raw)
To: David Miller; +Cc: joe, netdev
In-Reply-To: <20100615.102305.28825768.davem@davemloft.net>
Le mardi 15 juin 2010 à 10:23 -0700, David Miller a écrit :
> From: Joe Perches <joe@perches.com>
> Date: Tue, 15 Jun 2010 09:22:12 -0700
>
> > On Mon, 2010-06-14 at 22:59 -0700, David Miller wrote:
> >> But that to me just means that until git has a friendlier way to
> >> walk backwards through blame information for a line to skip past
> >> the cleanup crapola, we shouldn't make a lot of these kinds of
> >> changes.
> >
> > What do you think is missing from the gitk file viewer?
>
> I don't use the gitk file viewer, I use "git blame".
>
> Requiring a GUI to get at the information I need is unacceptable. I
> often work remotely and use text consoles for everything, even email
> and web browsing.
>
Same for me, I am very old school, and use a text editor I co-wrote in
1986 ;)
I just tried gitk, but ... where is 'git blame' information ?
Ah let me see : select *one* line and ask
'run git gui blame on this line'.
-> Error on sterr (text console) :
git: 'gui' is not a git command. See 'git --help'.
Did you mean one of these?
grep
init
pull
push
Hmm ?
git --version
git version 1.7.0.4
Oh well...
^ permalink raw reply
* IPv6 stops working after ethtool diagnostic
From: Tantilov, Emil S @ 2010-06-15 18:05 UTC (permalink / raw)
To: netdev@vger.kernel.org
Cc: Stephen Hemminger, davem@davemloft.net, Pieper, Jeffrey E,
Kirsher, Jeffrey T
We ran into a problem on net-next where IPv6 traffic fails after running ethtool -t:
Steps to reproduce:
1. ip addr show eth0
170: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:1b:21:53:0a:d6 brd ff:ff:ff:ff:ff:ff
inet 190.0.15.5/16 brd 190.0.255.255 scope global eth0
inet6 fec0::1505/112 scope site
valid_lft forever preferred_lft forever
inet6 fe80::21b:21ff:fe53:ad6/64 scope link
valid_lft forever preferred_lft forever
2. ping6 -I eth0 fec0::1314
PING fec0::1314(fec0::1314) from fec0::1505 eth0: 56 data bytes
64 bytes from fec0::1314: icmp_seq=1 ttl=64 time=0.130 ms
64 bytes from fec0::1314: icmp_seq=2 ttl=64 time=0.119 ms
64 bytes from fec0::1314: icmp_seq=3 ttl=64 time=0.167 ms
3 ethtool -t eth0 (ifconfig eth0 down/up has the same effect)
4. ping6 -I eth0 fec0::1314
connect: Network is unreachable
5. ip addr show eth0
170: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:1b:21:53:0a:d6 brd ff:ff:ff:ff:ff:ff
inet 190.0.15.5/16 brd 190.0.255.255 scope global eth0
inet6 fec0::1505/112 scope site
valid_lft forever preferred_lft forever
inet6 fe80::21b:21ff:fe53:ad6/64 scope link
valid_lft forever preferred_lft forever
Thanks,
Emil
^ permalink raw reply
* Re: IPv6 stops working after ethtool diagnostic
From: Ben Hutchings @ 2010-06-15 18:16 UTC (permalink / raw)
To: Tantilov, Emil S
Cc: netdev@vger.kernel.org, Stephen Hemminger, davem@davemloft.net,
Pieper, Jeffrey E, Kirsher, Jeffrey T
In-Reply-To: <EA929A9653AAE14F841771FB1DE5A1365FF43AAD0F@rrsmsx501.amr.corp.intel.com>
On Tue, 2010-06-15 at 12:05 -0600, Tantilov, Emil S wrote:
> We ran into a problem on net-next where IPv6 traffic fails after running ethtool -t:
Are you going to report which driver is responsible for eth0 or are are
you going to let us guess?
Ben.
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
^ permalink raw reply
* [PATCH net-next-2.6] inetpeer: RCU conversion
From: Eric Dumazet @ 2010-06-15 18:23 UTC (permalink / raw)
To: David Miller; +Cc: netdev, Paul E. McKenney
inetpeer currently uses an AVL tree protected by an rwlock.
It's possible to make most lookups use RCU
1) Add a struct rcu_head to struct inet_peer
2) add a lookup_rcu_bh() helper to perform lockless and opportunistic
lookup. This is a normal function, not a macro like lookup().
3) Add a limit to number of links followed by lookup_rcu_bh(). This is
needed in case we fall in a loop.
4) add an smp_wmb() in link_to_pool() right before node insert.
5) make unlink_from_pool() use atomic_cmpxchg() to make sure it can take
last reference to an inet_peer, since lockless readers could increase
refcount, even while we hold peers.lock.
6) Delay struct inet_peer freeing after rcu grace period so that
lookup_rcu_bh() cannot crash.
7) inet_getpeer() first attempts lockless lookup.
Note this lookup can fail even if target is in AVL tree, but a
concurrent writer can let tree in a non correct form.
If this attemps fails, lock is taken a regular lookup is performed
again.
8) convert peers.lock from rwlock to a spinlock
9) Remove SLAB_HWCACHE_ALIGN when peer_cachep is created, because
rcu_head adds 16 bytes on 64bit arches, doubling effective size (64 ->
128 bytes)
In a future patch, this is probably possible to revert this part, if rcu
field is put in an union to share space with rid, ip_id_count, tcp_ts &
tcp_ts_stamp. These fields being manipulated only with refcnt > 0.
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---
include/net/inetpeer.h | 1
net/ipv4/inetpeer.c | 164 ++++++++++++++++++++++-----------------
2 files changed, 96 insertions(+), 69 deletions(-)
diff --git a/include/net/inetpeer.h b/include/net/inetpeer.h
index 87b1df0..6174047 100644
--- a/include/net/inetpeer.h
+++ b/include/net/inetpeer.h
@@ -26,6 +26,7 @@ struct inet_peer {
atomic_t ip_id_count; /* IP ID for the next packet */
__u32 tcp_ts;
__u32 tcp_ts_stamp;
+ struct rcu_head rcu;
};
void inet_initpeers(void) __init;
diff --git a/net/ipv4/inetpeer.c b/net/ipv4/inetpeer.c
index 035673f..5f3846c 100644
--- a/net/ipv4/inetpeer.c
+++ b/net/ipv4/inetpeer.c
@@ -51,8 +51,8 @@
* lookups performed with disabled BHs.
*
* Serialisation issues.
- * 1. Nodes may appear in the tree only with the pool write lock held.
- * 2. Nodes may disappear from the tree only with the pool write lock held
+ * 1. Nodes may appear in the tree only with the pool lock held.
+ * 2. Nodes may disappear from the tree only with the pool lock held
* AND reference count being 0.
* 3. Nodes appears and disappears from unused node list only under
* "inet_peer_unused_lock".
@@ -80,11 +80,11 @@ static const struct inet_peer peer_fake_node = {
static struct {
struct inet_peer *root;
- rwlock_t lock;
+ spinlock_t lock;
int total;
} peers = {
.root = peer_avl_empty,
- .lock = __RW_LOCK_UNLOCKED(peers.lock),
+ .lock = __SPIN_LOCK_UNLOCKED(peers.lock),
.total = 0,
};
#define PEER_MAXDEPTH 40 /* sufficient for about 2^27 nodes */
@@ -129,7 +129,7 @@ void __init inet_initpeers(void)
peer_cachep = kmem_cache_create("inet_peer_cache",
sizeof(struct inet_peer),
- 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
+ 0, SLAB_PANIC,
NULL);
/* All the timers, started at system startup tend
@@ -153,16 +153,13 @@ static void unlink_from_unused(struct inet_peer *p)
/*
* Called with local BH disabled and the pool lock held.
- * _stack is known to be NULL or not at compile time,
- * so compiler will optimize the if (_stack) tests.
*/
#define lookup(_daddr, _stack) \
({ \
struct inet_peer *u, **v; \
- if (_stack != NULL) { \
- stackptr = _stack; \
- *stackptr++ = &peers.root; \
- } \
+ \
+ stackptr = _stack; \
+ *stackptr++ = &peers.root; \
for (u = peers.root; u != peer_avl_empty; ) { \
if (_daddr == u->v4daddr) \
break; \
@@ -170,14 +167,41 @@ static void unlink_from_unused(struct inet_peer *p)
v = &u->avl_left; \
else \
v = &u->avl_right; \
- if (_stack != NULL) \
- *stackptr++ = v; \
+ *stackptr++ = v; \
u = *v; \
} \
u; \
})
-/* Called with local BH disabled and the pool write lock held. */
+/*
+ * Called with rcu_read_lock_bh()
+ * Because we hold no lock against a writer, its quite possible we fall
+ * in an endless loop.
+ * But every pointer we follow is guaranteed to be valid thanks to RCU.
+ * We exit from this function if number of links exceeds PEER_MAXDEPTH
+ */
+static struct inet_peer *lookup_rcu_bh(__be32 daddr)
+{
+ struct inet_peer *u = rcu_dereference_bh(peers.root);
+ int count = 0;
+
+ while (u != peer_avl_empty) {
+ if (daddr == u->v4daddr) {
+ if (unlikely(!atomic_inc_not_zero(&u->refcnt)))
+ u = NULL;
+ return u;
+ }
+ if ((__force __u32)daddr < (__force __u32)u->v4daddr)
+ u = rcu_dereference_bh(u->avl_left);
+ else
+ u = rcu_dereference_bh(u->avl_right);
+ if (unlikely(++count == PEER_MAXDEPTH))
+ break;
+ }
+ return NULL;
+}
+
+/* Called with local BH disabled and the pool lock held. */
#define lookup_rightempty(start) \
({ \
struct inet_peer *u, **v; \
@@ -191,9 +215,10 @@ static void unlink_from_unused(struct inet_peer *p)
u; \
})
-/* Called with local BH disabled and the pool write lock held.
+/* Called with local BH disabled and the pool lock held.
* Variable names are the proof of operation correctness.
- * Look into mm/map_avl.c for more detail description of the ideas. */
+ * Look into mm/map_avl.c for more detail description of the ideas.
+ */
static void peer_avl_rebalance(struct inet_peer **stack[],
struct inet_peer ***stackend)
{
@@ -269,16 +294,22 @@ static void peer_avl_rebalance(struct inet_peer **stack[],
}
}
-/* Called with local BH disabled and the pool write lock held. */
+/* Called with local BH disabled and the pool lock held. */
#define link_to_pool(n) \
do { \
n->avl_height = 1; \
n->avl_left = peer_avl_empty; \
n->avl_right = peer_avl_empty; \
+ smp_wmb(); /* lockless readers can catch us now */ \
**--stackptr = n; \
peer_avl_rebalance(stack, stackptr); \
} while (0)
+static void inetpeer_free_rcu(struct rcu_head *head)
+{
+ kmem_cache_free(peer_cachep, container_of(head, struct inet_peer, rcu));
+}
+
/* May be called with local BH enabled. */
static void unlink_from_pool(struct inet_peer *p)
{
@@ -286,13 +317,13 @@ static void unlink_from_pool(struct inet_peer *p)
do_free = 0;
- write_lock_bh(&peers.lock);
+ spin_lock_bh(&peers.lock);
/* Check the reference counter. It was artificially incremented by 1
- * in cleanup() function to prevent sudden disappearing. If the
- * reference count is still 1 then the node is referenced only as `p'
- * here and from the pool. So under the exclusive pool lock it's safe
- * to remove the node and free it later. */
- if (atomic_read(&p->refcnt) == 1) {
+ * in cleanup() function to prevent sudden disappearing. If we can
+ * atomically (because of lockless readers) take this last reference,
+ * it's safe to remove the node and free it later.
+ */
+ if (atomic_cmpxchg(&p->refcnt, 1, 0) == 1) {
struct inet_peer **stack[PEER_MAXDEPTH];
struct inet_peer ***stackptr, ***delp;
if (lookup(p->v4daddr, stack) != p)
@@ -321,17 +352,18 @@ static void unlink_from_pool(struct inet_peer *p)
peers.total--;
do_free = 1;
}
- write_unlock_bh(&peers.lock);
+ spin_unlock_bh(&peers.lock);
if (do_free)
- kmem_cache_free(peer_cachep, p);
+ call_rcu_bh(&p->rcu, inetpeer_free_rcu);
else
/* The node is used again. Decrease the reference counter
* back. The loop "cleanup -> unlink_from_unused
* -> unlink_from_pool -> putpeer -> link_to_unused
* -> cleanup (for the same node)"
* doesn't really exist because the entry will have a
- * recent deletion time and will not be cleaned again soon. */
+ * recent deletion time and will not be cleaned again soon.
+ */
inet_putpeer(p);
}
@@ -375,62 +407,56 @@ static int cleanup_once(unsigned long ttl)
/* Called with or without local BH being disabled. */
struct inet_peer *inet_getpeer(__be32 daddr, int create)
{
- struct inet_peer *p, *n;
+ struct inet_peer *p;
struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr;
- /* Look up for the address quickly. */
- read_lock_bh(&peers.lock);
- p = lookup(daddr, NULL);
- if (p != peer_avl_empty)
- atomic_inc(&p->refcnt);
- read_unlock_bh(&peers.lock);
+ /* Look up for the address quickly, lockless.
+ * Because of a concurrent writer, we might not find an existing entry.
+ */
+ rcu_read_lock_bh();
+ p = lookup_rcu_bh(daddr);
+ rcu_read_unlock_bh();
+
+ if (p) {
+ /* The existing node has been found.
+ * Remove the entry from unused list if it was there.
+ */
+ unlink_from_unused(p);
+ return p;
+ }
+ /* retry an exact lookup, taking the lock before.
+ * At least, nodes should be hot in our cache.
+ */
+ spin_lock_bh(&peers.lock);
+ p = lookup(daddr, stack);
if (p != peer_avl_empty) {
- /* The existing node has been found. */
+ atomic_inc(&p->refcnt);
+ spin_unlock_bh(&peers.lock);
/* Remove the entry from unused list if it was there. */
unlink_from_unused(p);
return p;
}
-
- if (!create)
- return NULL;
-
- /* Allocate the space outside the locked region. */
- n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC);
- if (n == NULL)
- return NULL;
- n->v4daddr = daddr;
- atomic_set(&n->refcnt, 1);
- atomic_set(&n->rid, 0);
- atomic_set(&n->ip_id_count, secure_ip_id(daddr));
- n->tcp_ts_stamp = 0;
-
- write_lock_bh(&peers.lock);
- /* Check if an entry has suddenly appeared. */
- p = lookup(daddr, stack);
- if (p != peer_avl_empty)
- goto out_free;
-
- /* Link the node. */
- link_to_pool(n);
- INIT_LIST_HEAD(&n->unused);
- peers.total++;
- write_unlock_bh(&peers.lock);
+ p = create ? kmem_cache_alloc(peer_cachep, GFP_ATOMIC) : NULL;
+ if (p) {
+ p->v4daddr = daddr;
+ atomic_set(&p->refcnt, 1);
+ atomic_set(&p->rid, 0);
+ atomic_set(&p->ip_id_count, secure_ip_id(daddr));
+ p->tcp_ts_stamp = 0;
+ INIT_LIST_HEAD(&p->unused);
+
+
+ /* Link the node. */
+ link_to_pool(p);
+ peers.total++;
+ }
+ spin_unlock_bh(&peers.lock);
if (peers.total >= inet_peer_threshold)
/* Remove one less-recently-used entry. */
cleanup_once(0);
- return n;
-
-out_free:
- /* The appropriate node is already in the pool. */
- atomic_inc(&p->refcnt);
- write_unlock_bh(&peers.lock);
- /* Remove the entry from unused list if it was there. */
- unlink_from_unused(p);
- /* Free preallocated the preallocated node. */
- kmem_cache_free(peer_cachep, n);
return p;
}
^ permalink raw reply related
* RE: IPv6 stops working after ethtool diagnostic
From: Tantilov, Emil S @ 2010-06-15 18:25 UTC (permalink / raw)
To: Ben Hutchings
Cc: netdev@vger.kernel.org, Stephen Hemminger, davem@davemloft.net,
Pieper, Jeffrey E, Kirsher, Jeffrey T
In-Reply-To: <1276625792.2078.12.camel@achroite.uk.solarflarecom.com>
Ben Hutchings wrote:
> On Tue, 2010-06-15 at 12:05 -0600, Tantilov, Emil S wrote:
>> We ran into a problem on net-next where IPv6 traffic fails after
>> running ethtool -t:
>
> Are you going to report which driver is responsible for eth0 or are
> are you going to let us guess?
Yeah - should've mention that. In my tests so far this does not appear to be driver specific issue. I was able to reproduce with e1000e, igb, ixgbe and bnx2.
Thanks,
Emil
^ permalink raw reply
* Re: [RFC patch net/next] net: Hoist assigns from if?
From: Joe Perches @ 2010-06-15 18:28 UTC (permalink / raw)
To: Eric Dumazet; +Cc: David Miller, netdev
In-Reply-To: <1276625060.2541.166.camel@edumazet-laptop>
On Tue, 2010-06-15 at 20:04 +0200, Eric Dumazet wrote:
> I am very old school, and use a text editor I co-wrote in
> 1986 ;)
I gave up writing my own text editors in the 70's,
but finger habits are hard to break.
> Hmm ?
Move up to the (late?) 80's... ;)
> Oh well...
Perhaps a new git blame based command would be useful
git blame -L <range> ---history
kind of an expanded, human friendly --incremental.
Show how the file range appeared with each previous revision.
^ permalink raw reply
* Re: [PATCH 05/12] phylib: Allow reading and writing a mii bus from atomic context.
From: Grant Likely @ 2010-06-15 18:29 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, devicetree-discuss, linuxppc-dev, linux-arm-kernel,
Krzysztof Halasa, Thomas Gleixner
In-Reply-To: <20100615170806.GB10668@riccoc20.at.omicron.at>
On Tue, Jun 15, 2010 at 11:08 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> On Tue, Jun 15, 2010 at 10:43:08AM -0600, Grant Likely wrote:
>> On Tue, Jun 15, 2010 at 10:08 AM, Richard Cochran
>> <richardcochran@gmail.com> wrote:
>> > In order to support hardware time stamping from a PHY, it is necessary to
>> > read from the PHY while running in_interrupt(). This patch allows a mii
>> > bus to operate in an atomic context. An mii_bus driver may declare itself
>> > capable for this mode. Drivers which do not do this will remain with the
>> > default that bus operations may sleep.
>> >
>> > Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
>>
>> Last I checked, the MDIO bus is very slow. Is this really a good
>> idea? How much latency does MDIO access have on the hardware you are
>> working with?
>
> Yes, MDIO access is slow, and it can vary (eg bit banging
> implementations). It clear that getting PHY timestamps is costly, but
> for applications that want PTP synchronization, one is willing to pay
> the price.
>
>> I also don't like the idea of taking a spin lock during MDIO
>> operations, and the dual locking mode in the core code.
>
> Originally, the phylib used a spinlock for this. It was replaced with
> a mutex in 35b5f6b1a82b5c586e0b24c711dc6ba944e88ef1 in order to
> accommodate mdio busses that may need to sleep. So, keeping the option
> to use a spinlock is similar to the previous implementation.
That's right, and I fully agree with that change. To me, going back
to allowing spin locks is a regression because it adds a new source of
scheduling latency. Using a mutex forces users to take into account
the slow nature of MDIO access. For existing callers, this isn't a
problem because they already are designed for this characteristic. A
new user which depends on atomic access should use a different API
which doesn't take the lock with the understanding that it is may
return a failure if it doesn't support it or if it cannot perform the
operation atomically.
That still leaves the troubling MDIO induced latency issue.
g.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [0/8] netpoll/bridge fixes
From: David Miller @ 2010-06-15 18:39 UTC (permalink / raw)
To: herbert; +Cc: shemminger, mst, frzhang, netdev, amwang, mpm
In-Reply-To: <20100611021142.GA24490@gondor.apana.org.au>
From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Fri, 11 Jun 2010 12:11:42 +1000
> On Fri, Jun 11, 2010 at 08:48:39AM +1000, Herbert Xu wrote:
>> On Thu, Jun 10, 2010 at 02:59:15PM -0700, Stephen Hemminger wrote:
>> >
>> > Okay, then add a comment where in_irq is used?
>>
>> Actually let me put it into a wrapper. I'll respin the patches.
>
> OK here is a repost. And this time it really is 8 patches :)
> I've tested it lightly.
All applied to net-next-2.6, thanks Herbert.
^ permalink raw reply
* Re: [PATCH 11/12] ptp: Added a clock driver for the IXP46x.
From: Grant Likely @ 2010-06-15 18:41 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Krzysztof Halasa
In-Reply-To: <0c03ef3e283c6b3ef58feaf1f77ccb0fd605010b.1276615626.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
On Tue, Jun 15, 2010 at 10:10 AM, Richard Cochran
<richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> This patch adds a driver for the hardware time stamping unit found on the
> IXP465. Only the basic clock operations are implemented.
>
> Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
Hi Richard,
Comments below...
> ---
> arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 67 +++++++
> drivers/net/arm/ixp4xx_eth.c | 194 +++++++++++++++++++++
> drivers/ptp/Kconfig | 13 ++
> drivers/ptp/Makefile | 1 +
> drivers/ptp/ptp_ixp46x.c | 231 +++++++++++++++++++++++++
> 5 files changed, 506 insertions(+), 0 deletions(-)
> create mode 100644 arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
> create mode 100644 drivers/ptp/ptp_ixp46x.c
>
> diff --git a/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
> new file mode 100644
> index 0000000..7fb02b6
> --- /dev/null
> +++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
> @@ -0,0 +1,67 @@
> +/*
> + * PTP 1588 clock using the IXP46X
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 _IXP46X_TS_H_
> +#define _IXP46X_TS_H_
> +
> +#define DEFAULT_ADDEND 0xF0000029
> +#define TICKS_NS_SHIFT 4
> +
> +struct ixp46x_channel_ctl {
> + u32 Ch_Control; /* 0x40 Time Synchronization Channel Control */
> + u32 Ch_Event; /* 0x44 Time Synchronization Channel Event */
> + u32 TxSnapLo; /* 0x48 Transmit Snapshot Low Register */
> + u32 TxSnapHi; /* 0x4C Transmit Snapshot High Register */
> + u32 RxSnapLo; /* 0x50 Receive Snapshot Low Register */
> + u32 RxSnapHi; /* 0x54 Receive Snapshot High Register */
> + u32 SrcUUIDLo; /* 0x58 Source UUID0 Low Register */
> + u32 SrcUUIDHi; /* 0x5C Sequence Identifier/Source UUID0 High */
> +};
Nitpick. We use all lower case names for structures in Linux.
> +
> +struct ixp46x_ts_regs {
> + u32 Control; /* 0x00 Time Sync Control Register */
> + u32 Event; /* 0x04 Time Sync Event Register */
> + u32 Addend; /* 0x08 Time Sync Addend Register */
> + u32 Accum; /* 0x0C Time Sync Accumulator Register */
> + u32 Test; /* 0x10 Time Sync Test Register */
> + u32 Unused; /* 0x14 */
> + u32 RSysTime_Lo; /* 0x18 RawSystemTime_Low Register */
> + u32 RSysTimeHi; /* 0x1C RawSystemTime_High Register */
> + u32 SysTimeLo; /* 0x20 SystemTime_Low Register */
> + u32 SysTimeHi; /* 0x24 SystemTime_High Register */
> + u32 TrgtLo; /* 0x28 TargetTime_Low Register */
> + u32 TrgtHi; /* 0x2C TargetTime_High Register */
> + u32 ASMSLo; /* 0x30 Auxiliary Slave Mode Snapshot Low */
> + u32 ASMSHi; /* 0x34 Auxiliary Slave Mode Snapshot High */
> + u32 AMMSLo; /* 0x38 Auxiliary Master Mode Snapshot Low */
> + u32 AMMSHi; /* 0x3C Auxiliary Master Mode Snapshot High */
> +
> + struct ixp46x_channel_ctl channel[3];
> +};
> +
> +/* 0x40 Time Synchronization Channel Control Register Bits */
> +#define MASTER_MODE (1<<0)
> +#define TIMESTAMP_ALL (1<<1)
> +
> +/* 0x44 Time Synchronization Channel Event Register Bits */
> +#define TX_SNAPSHOT_LOCKED (1<<0)
> +#define RX_SNAPSHOT_LOCKED (1<<1)
> +
> +#endif
> diff --git a/drivers/net/arm/ixp4xx_eth.c b/drivers/net/arm/ixp4xx_eth.c
> index 4f1cc71..2201960 100644
> --- a/drivers/net/arm/ixp4xx_eth.c
> +++ b/drivers/net/arm/ixp4xx_eth.c
> @@ -30,9 +30,12 @@
> #include <linux/etherdevice.h>
> #include <linux/io.h>
> #include <linux/kernel.h>
> +#include <linux/net_tstamp.h>
> #include <linux/phy.h>
> #include <linux/platform_device.h>
> +#include <linux/ptp_classify.h>
> #include <linux/slab.h>
> +#include <mach/ixp46x_ts.h>
> #include <mach/npe.h>
> #include <mach/qmgr.h>
>
> @@ -67,6 +70,14 @@
> #define RXFREE_QUEUE(port_id) (NPE_ID(port_id) + 26)
> #define TXDONE_QUEUE 31
>
> +#define PTP_SLAVE_MODE 1
> +#define PTP_MASTER_MODE 2
> +#define PORT2CHANNEL(p) 1
> +/*
> + * PHYSICAL_ID(p->id) ?
> + * TODO - Figure out correct mapping.
> + */
> +
> /* TX Control Registers */
> #define TX_CNTRL0_TX_EN 0x01
> #define TX_CNTRL0_HALFDUPLEX 0x02
> @@ -171,6 +182,8 @@ struct port {
> int id; /* logical port ID */
> int speed, duplex;
> u8 firmware[4];
> + int hwts_tx_en;
> + int hwts_rx_en;
> };
>
> /* NPE message structure */
> @@ -246,6 +259,170 @@ static int ports_open;
> static struct port *npe_port_tab[MAX_NPES];
> static struct dma_pool *dma_pool;
>
> +static struct sock_filter ptp_filter[] = {
> + PTP_FILTER
> +};
> +
> +static int match(struct sk_buff *skb, u16 uid_hi, u32 uid_lo, u16 seq)
> +{
> + unsigned int type;
> + u16 *hi, *id;
> + u8 *lo, *data = skb->data;
> +
> + type = sk_run_filter(skb, ptp_filter, ARRAY_SIZE(ptp_filter));
> +
> + if (PTP_CLASS_V1_IPV4 == type) {
> +
> + id = (u16 *)(data + 42 + 30);
> + hi = (u16 *)(data + 42 + 22);
> + lo = data + 42 + 24;
> +
> + return (uid_hi == *hi &&
> + 0 == memcmp(&uid_lo, lo, sizeof(uid_lo)) &&
> + seq == *id);
> + }
> +
> + return 0;
> +}
> +
> +static void do_rx_timestamp(struct port *port, struct sk_buff *skb)
> +{
> + struct skb_shared_hwtstamps *shhwtstamps;
> + struct ixp46x_ts_regs *regs;
> + u64 ns;
> + u32 ch, hi, lo, val;
> + u16 uid, seq;
> +
> + if (!port->hwts_rx_en)
> + return;
> +
> + ch = PORT2CHANNEL(port);
> +
> + regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
> +
> + val = __raw_readl(®s->channel[ch].Ch_Event);
> +
> + if (!(val & RX_SNAPSHOT_LOCKED))
> + return;
> +
> + lo = __raw_readl(®s->channel[ch].SrcUUIDLo);
> + hi = __raw_readl(®s->channel[ch].SrcUUIDHi);
> +
> + uid = hi & 0xffff;
> + seq = (hi >> 16) & 0xffff;
> +
> + if (!match(skb, htons(uid), htonl(lo), htons(seq)))
> + goto out;
> +
> + lo = __raw_readl(®s->channel[ch].RxSnapLo);
> + hi = __raw_readl(®s->channel[ch].RxSnapHi);
> + ns = ((u64) hi) << 32;
> + ns |= lo;
> + ns <<= TICKS_NS_SHIFT;
> +
> + shhwtstamps = skb_hwtstamps(skb);
> + memset(shhwtstamps, 0, sizeof(*shhwtstamps));
> + shhwtstamps->hwtstamp = ns_to_ktime(ns);
> +out:
> + __raw_writel(RX_SNAPSHOT_LOCKED, ®s->channel[ch].Ch_Event);
> +}
> +
> +static void do_tx_timestamp(struct port *port, struct sk_buff *skb)
> +{
> +#ifdef __ARMEB__
> + struct skb_shared_hwtstamps shhwtstamps;
> + struct ixp46x_ts_regs *regs;
> + union skb_shared_tx *shtx;
> + u64 ns;
> + u32 ch, cnt, hi, lo, val;
> +
> + shtx = skb_tx(skb);
> +
> + if (!shtx->in_progress)
> + return;
> +
> + ch = PORT2CHANNEL(port);
> +
> + regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
> +
> + /*
> + * This really stinks, but we have to poll for the Tx time stamp.
> + * Usually, the time stamp is ready after 4 to 6 microseconds.
> + */
> + for (cnt = 0; cnt < 100; cnt++) {
> + val = __raw_readl(®s->channel[ch].Ch_Event);
> + if (val & TX_SNAPSHOT_LOCKED)
> + break;
> + udelay(1);
You want to get stuff as fast as possible, but there is a udelay()
that just chews up CPU time. Would cpu_relax() be sufficient with a
time-based exit condition in the loop?
> + }
> + if (!(val & TX_SNAPSHOT_LOCKED)) {
> + shtx->in_progress = 0;
> + return;
> + }
> +
> + lo = __raw_readl(®s->channel[ch].TxSnapLo);
> + hi = __raw_readl(®s->channel[ch].TxSnapHi);
> + ns = ((u64) hi) << 32;
> + ns |= lo;
> + ns <<= TICKS_NS_SHIFT;
> +
> + memset(&shhwtstamps, 0, sizeof(shhwtstamps));
> + shhwtstamps.hwtstamp = ns_to_ktime(ns);
> + skb_tstamp_tx(skb, &shhwtstamps);
> +
> + __raw_writel(TX_SNAPSHOT_LOCKED, ®s->channel[ch].Ch_Event);
> +#endif
> +}
> +
> +static int hwtstamp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
> +{
> + struct hwtstamp_config cfg;
> + struct ixp46x_ts_regs *regs;
> + struct port *port = netdev_priv(netdev);
> + int ch;
> +
> + if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
> + return -EFAULT;
> +
> + if (cfg.flags) /* reserved for future extensions */
> + return -EINVAL;
> +
> + ch = PORT2CHANNEL(port);
> + regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
> +
> + switch (cfg.tx_type) {
> + case HWTSTAMP_TX_OFF:
> + port->hwts_tx_en = 0;
> + break;
> + case HWTSTAMP_TX_ON:
> + port->hwts_tx_en = 1;
> + break;
> + default:
> + return -ERANGE;
> + }
> +
> + switch (cfg.rx_filter) {
> + case HWTSTAMP_FILTER_NONE:
> + port->hwts_rx_en = 0;
> + break;
> + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
> + port->hwts_rx_en = PTP_SLAVE_MODE;
> + __raw_writel(0, ®s->channel[ch].Ch_Control);
> + break;
> + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
> + port->hwts_rx_en = PTP_MASTER_MODE;
> + __raw_writel(MASTER_MODE, ®s->channel[ch].Ch_Control);
> + break;
> + default:
> + return -ERANGE;
> + }
> +
> + /* Clear out any old time stamps. */
> + __raw_writel(TX_SNAPSHOT_LOCKED | RX_SNAPSHOT_LOCKED,
> + ®s->channel[ch].Ch_Event);
> +
> + return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
> +}
>
> static int ixp4xx_mdio_cmd(struct mii_bus *bus, int phy_id, int location,
> int write, u16 cmd)
> @@ -573,6 +750,7 @@ static int eth_poll(struct napi_struct *napi, int budget)
>
> debug_pkt(dev, "eth_poll", skb->data, skb->len);
>
> + do_rx_timestamp(port, skb);
> skb->protocol = eth_type_trans(skb, dev);
> dev->stats.rx_packets++;
> dev->stats.rx_bytes += skb->len;
> @@ -652,6 +830,7 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev)
> void *mem;
> u32 phys;
> struct desc *desc;
> + union skb_shared_tx *shtx;
>
> #if DEBUG_TX
> printk(KERN_DEBUG "%s: eth_xmit\n", dev->name);
> @@ -665,6 +844,10 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev)
>
> debug_pkt(dev, "eth_xmit", skb->data, skb->len);
>
> + shtx = skb_tx(skb);
> + if (unlikely(shtx->hardware && port->hwts_tx_en))
> + shtx->in_progress = 1;
> +
> len = skb->len;
> #ifdef __ARMEB__
> offset = 0; /* no need to keep alignment */
> @@ -728,6 +911,9 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev)
> #if DEBUG_TX
> printk(KERN_DEBUG "%s: eth_xmit end\n", dev->name);
> #endif
> +
> + do_tx_timestamp(port, skb);
> +
> return NETDEV_TX_OK;
> }
>
> @@ -783,6 +969,9 @@ static int eth_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
> if (!netif_running(dev))
> return -EINVAL;
>
> + if (cpu_is_ixp46x() && cmd == SIOCSHWTSTAMP)
> + return hwtstamp_ioctl(dev, req, cmd);
> +
> return phy_mii_ioctl(port->phydev, req, cmd);
> }
>
> @@ -1171,6 +1360,11 @@ static int __devinit eth_init_one(struct platform_device *pdev)
> char phy_id[MII_BUS_ID_SIZE + 3];
> int err;
>
> + if (sk_chk_filter(ptp_filter, ARRAY_SIZE(ptp_filter))) {
> + pr_err("ixp4xx_eth: bad ptp filter\n");
> + return -EINVAL;
> + }
> +
> if (!(dev = alloc_etherdev(sizeof(struct port))))
> return -ENOMEM;
>
> diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
> index 3b7bd73..9fb35f6 100644
> --- a/drivers/ptp/Kconfig
> +++ b/drivers/ptp/Kconfig
> @@ -48,4 +48,17 @@ config PTP_1588_CLOCK_GIANFAR
> To compile this driver as a module, choose M here: the module
> will be called gianfar_ptp.
>
> +config PTP_1588_CLOCK_IXP46X
> + tristate "Intel IXP46x as PTP clock"
> + depends on PTP_1588_CLOCK
> + depends on IXP4XX_ETH
> + help
> + This driver adds support for using the IXP46X as a PTP
> + clock. This clock is only useful if your PTP programs are
> + getting hardware time stamps on the PTP Ethernet packets
> + using the SO_TIMESTAMPING API.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called ptp_ixp46x.
> +
> endmenu
> diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
> index 1651d52..5018f58 100644
> --- a/drivers/ptp/Makefile
> +++ b/drivers/ptp/Makefile
> @@ -4,3 +4,4 @@
>
> obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
> obj-$(CONFIG_PTP_1588_CLOCK_LINUX) += ptp_linux.o
> +obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o
> diff --git a/drivers/ptp/ptp_ixp46x.c b/drivers/ptp/ptp_ixp46x.c
> new file mode 100644
> index 0000000..22c5bc3
> --- /dev/null
> +++ b/drivers/ptp/ptp_ixp46x.c
> @@ -0,0 +1,231 @@
> +/*
> + * PTP 1588 clock using the IXP46X
> + *
> + * Copyright (C) 2010 OMICRON electronics GmbH
> + *
> + * 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 <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +
> +#include <linux/ptp_clock_kernel.h>
> +#include <mach/ixp46x_ts.h>
> +
> +DEFINE_SPINLOCK(register_lock);
> +
> +/*
> + * Register access functions
> + */
> +
> +static inline u32 ixp_read(volatile unsigned __iomem *addr)
> +{
> + u32 val;
> + val = __raw_readl(addr);
> + return val;
> +}
return __raw_readl(addr) perhaps?
> +
> +static inline void ixp_write(volatile unsigned __iomem *addr, u32 val)
> +{
> + __raw_writel(val, addr);
> +}
> +
> +static u64 sys_time_read(struct ixp46x_ts_regs *regs)
> +{
> + u64 ns;
> + u32 lo, hi;
> +
> + lo = ixp_read(®s->SysTimeLo);
> + hi = ixp_read(®s->SysTimeHi);
> +
> + ns = ((u64) hi) << 32;
> + ns |= lo;
> + ns <<= TICKS_NS_SHIFT;
> +
> + return ns;
> +}
> +
> +static void sys_time_write(struct ixp46x_ts_regs *regs, u64 ns)
Should use the ptp_ixp_ prefix on these functions too.
> +{
> + u32 hi, lo;
> +
> + ns >>= TICKS_NS_SHIFT;
> + hi = ns >> 32;
> + lo = ns & 0xffffffff;
> +
> + ixp_write(®s->SysTimeLo, lo);
> + ixp_write(®s->SysTimeHi, hi);
> +}
> +
> +/*
> + * PTP clock operations
> + */
> +
> +static int ptp_ixp_adjfreq(void *priv, s32 ppb)
> +{
> + u64 adj;
> + u32 diff, addend;
> + int neg_adj = 0;
> + struct ixp46x_ts_regs *regs = priv;
> +
> + if (!ppb)
> + return 0;
> +
> + if (ppb < 0) {
> + neg_adj = 1;
> + ppb = -ppb;
> + }
> + addend = DEFAULT_ADDEND;
> + adj = addend;
> + adj *= ppb;
> + diff = div_u64(adj, 1000000000ULL);
> +
> + addend = neg_adj ? addend - diff : addend + diff;
> +
> + ixp_write(®s->Addend, addend);
> +
> + return 0;
> +}
> +
> +static int ptp_ixp_adjtime(void *priv, struct timespec *ts)
> +{
> + s64 delta, now;
> + unsigned long flags;
> + struct ixp46x_ts_regs *regs = priv;
> +
> + delta = 1000000000LL * ts->tv_sec;
> + delta += ts->tv_nsec;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + now = sys_time_read(regs);
> + now += delta;
> + sys_time_write(regs, now);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + return 0;
> +}
> +
> +static int ptp_ixp_gettime(void *priv, struct timespec *ts)
> +{
> + u64 ns;
> + u32 remainder;
> + unsigned long flags;
> + struct ixp46x_ts_regs *regs = priv;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + ns = sys_time_read(regs);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
> + ts->tv_nsec = remainder;
> + return 0;
> +}
> +
> +static int ptp_ixp_settime(void *priv, struct timespec *ts)
> +{
> + u64 ns;
> + unsigned long flags;
> + struct ixp46x_ts_regs *regs = priv;
> +
> + ns = ts->tv_sec * 1000000000ULL;
> + ns += ts->tv_nsec;
> +
> + spin_lock_irqsave(®ister_lock, flags);
> +
> + sys_time_write(regs, ns);
> +
> + spin_unlock_irqrestore(®ister_lock, flags);
> +
> + return 0;
> +}
> +
> +static int ptp_ixp_gettimer(void *priv, int index, struct itimerspec *ts)
> +{
> + /* We do not offer any ancillary features at all. */
> + return -EOPNOTSUPP;
> +}
> +
> +static int ptp_ixp_settimer(void *p, int i, int abs, struct itimerspec *ts)
> +{
> + /* We do not offer any ancillary features at all. */
> + return -EOPNOTSUPP;
> +}
> +
> +static int ptp_ixp_enable(void *priv, struct ptp_clock_request *rq, int on)
> +{
> + /* We do not offer any ancillary features at all. */
> + return -EOPNOTSUPP;
> +}
> +
> +static struct ptp_clock_info ptp_ixp_caps = {
> + .owner = THIS_MODULE,
> + .name = "IXP46X timer",
> + .max_adj = 512000,
> + .n_alarm = 0,
> + .n_ext_ts = 0,
> + .n_per_out = 0,
> + .pps = 0,
> + .priv = NULL,
If the value is '0' or NULL, just leave them out of the structure initializer.
> + .adjfreq = ptp_ixp_adjfreq,
> + .adjtime = ptp_ixp_adjtime,
> + .gettime = ptp_ixp_gettime,
> + .settime = ptp_ixp_settime,
> + .gettimer = ptp_ixp_gettimer,
> + .settimer = ptp_ixp_settimer,
> + .enable = ptp_ixp_enable,
> +};
> +
> +/* module operations */
> +
> +static struct {
> + struct ixp46x_ts_regs *regs;
> + struct ptp_clock *ptp_clock;
> +} ixp_clock;
> +
> +static void __exit ptp_ixp_exit(void)
> +{
> + ptp_clock_unregister(ixp_clock.ptp_clock);
> +}
> +
> +static int __init ptp_ixp_init(void)
> +{
> + ixp_clock.regs =
> + (struct ixp46x_ts_regs __iomem *)IXP4XX_TIMESYNC_BASE_VIRT;
> +
> + ptp_ixp_caps.priv = ixp_clock.regs;
> +
> + ixp_clock.ptp_clock = ptp_clock_register(&ptp_ixp_caps);
> +
> + if (IS_ERR(ixp_clock.ptp_clock))
> + return PTR_ERR(ixp_clock.ptp_clock);
> +
> + ixp_write(&ixp_clock.regs->Addend, DEFAULT_ADDEND);
> +
> + return 0;
> +}
> +
> +module_init(ptp_ixp_init);
> +module_exit(ptp_ixp_exit);
Keep module_init and module_exit with their respective function declarations.
g.
^ permalink raw reply
* Re: [PATCH net-next-2.6 1/3] net: add rx_handler data pointer
From: David Miller @ 2010-06-15 18:49 UTC (permalink / raw)
To: jpirko; +Cc: netdev, shemminger, kaber, eric.dumazet
In-Reply-To: <20100610133458.GD2618@psychotron.lab.eng.brq.redhat.com>
From: Jiri Pirko <jpirko@redhat.com>
Date: Thu, 10 Jun 2010 15:34:59 +0200
> Add possibility to register rx_handler data pointer along with a rx_handler.
>
> Signed-off-by: Jiri Pirko <jpirko@redhat.com>
Applied.
^ permalink raw reply
* Re: [PATCH net-next-2.6 2/3] macvlan: use rx_handler_data pointer to store macvlan_port pointer V2
From: David Miller @ 2010-06-15 18:50 UTC (permalink / raw)
To: jpirko; +Cc: netdev, shemminger, kaber, eric.dumazet
In-Reply-To: <20100615132756.GA3797@psychotron.lab.eng.brq.redhat.com>
From: Jiri Pirko <jpirko@redhat.com>
Date: Tue, 15 Jun 2010 15:27:57 +0200
> Register macvlan_port pointer as rx_handler data pointer. As macvlan_port is
> removed from struct net_device, another netdev priv_flag is added to indicate
> the device serves as a macvlan port.
>
> Signed-off-by: Jiri Pirko <jpirko@redhat.com>
> Acked-by: Patrick McHardy <kaber@trash.net>
Applied.
^ permalink raw reply
* Re: [PATCH net-next-2.6 3/3 v3] bridge: use rx_handler_data pointer to store net_bridge_port pointer
From: David Miller @ 2010-06-15 18:50 UTC (permalink / raw)
To: jpirko; +Cc: netdev, shemminger, kaber, eric.dumazet
In-Reply-To: <20100615165043.GA2623@psychotron.redhat.com>
From: Jiri Pirko <jpirko@redhat.com>
Date: Tue, 15 Jun 2010 18:50:45 +0200
> Register net_bridge_port pointer as rx_handler data pointer. As br_port is
> removed from struct net_device, another netdev priv_flag is added to indicate
> the device serves as a bridge port. Also rcuized pointers are now correctly
> dereferenced in br_fdb.c and in netfilter parts.
>
> Signed-off-by: Jiri Pirko <jpirko@redhat.com>
Applied.
^ permalink raw reply
* Re: [PATCH 12/12] ptp: Added a clock driver for the National Semiconductor PHYTER.
From: Grant Likely @ 2010-06-15 18:49 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, devicetree-discuss, linuxppc-dev, linux-arm-kernel,
Krzysztof Halasa
In-Reply-To: <37f5015b186bdd51f8451ade042f90c8b39a5cc8.1276615626.git.richard.cochran@omicron.at>
On Tue, Jun 15, 2010 at 10:10 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> This patch adds support for the PTP clock found on the DP83640. Only the
> basic clock operations have been implemented.
>
> Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
> ---
> drivers/net/phy/Kconfig | 11 +++
> drivers/net/phy/dp83640.c | 158 ++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 168 insertions(+), 1 deletions(-)
>
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index 430cab1..507c68a 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -79,9 +79,20 @@ config NATIONAL_PHY
>
> config DP83640_PHY
> tristate "Driver for the National Semiconductor DP83640 PHYTER"
> + depends on PTP_1588_CLOCK
> + depends on NETWORK_PHY_TIMESTAMPING
Won't this break things for existing DP83640 users?
> ---help---
> Supports the DP83640 PHYTER with IEEE 1588 features.
>
> + This driver adds support for using the DP83640 as a PTP
> + clock. This clock is only useful if your PTP programs are
> + getting hardware time stamps on the PTP Ethernet packets
> + using the SO_TIMESTAMPING API.
> +
> + In order for this to work, your MAC driver must also
> + implement the the skb_tx_timetamp() and skb_rx_timetamp()
> + functions.
> +
> config STE10XP
> depends on PHYLIB
> tristate "Driver for STMicroelectronics STe10Xp PHYs"
> diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
> index a3217ea..21eadc3 100644
> --- a/drivers/net/phy/dp83640.c
> +++ b/drivers/net/phy/dp83640.c
> @@ -26,6 +26,7 @@
> #include <linux/netdevice.h>
> #include <linux/phy.h>
> #include <linux/ptp_classify.h>
> +#include <linux/ptp_clock_kernel.h>
>
> #include "dp83640_reg.h"
>
> @@ -45,10 +46,13 @@ struct rxts {
> };
>
> struct dp83640_private {
> + struct phy_device *phydev;
> int hwts_tx_en;
> int hwts_rx_en;
> int layer;
> int version;
> + /* protects PTP_TDR register from concurrent access */
> + spinlock_t ptp_tdr_lock;
> /* protects extended registers from concurrent access */
> spinlock_t extreg_lock;
> int page;
> @@ -60,6 +64,9 @@ struct dp83640_private {
>
> /* globals */
>
> +static struct ptp_clock *dp83640_clock;
> +DEFINE_SPINLOCK(clock_lock); /* protects the one and only dp83640_clock */
Why only one? Is it not possible to have 2 of these PHYs in a system?
> +
> static struct sock_filter ptp_filter[] = {
> PTP_FILTER
> };
> @@ -99,6 +106,129 @@ static void ext_write(struct phy_device *phydev, int page, u32 regnum, u16 val)
> spin_unlock(&dp83640->extreg_lock);
> }
>
> +static int tdr_write(struct dp83640_private *dp83640,
> + struct timespec *ts, u16 cmd)
> +{
> + struct phy_device *phydev = dp83640->phydev;
> +
> + spin_lock(&dp83640->ptp_tdr_lock);
> +
> + ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec & 0xffff);/* ns[15:0] */
> + ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec >> 16); /* ns[31:16] */
> + ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec & 0xffff); /* sec[15:0] */
> + ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec >> 16); /* sec[31:16] */
> +
> + ext_write(phydev, PAGE4, PTP_CTL, cmd);
> +
> + spin_unlock(&dp83640->ptp_tdr_lock);
> +
> + return 0;
> +}
> +
> +/* ptp clock methods */
> +
> +static int ptp_dp83640_adjfreq(void *priv, s32 ppb)
> +{
> + struct dp83640_private *dp83640 = priv;
> + struct phy_device *phydev = dp83640->phydev;
> + u64 rate;
> + int neg_adj = 0;
> + u16 hi, lo;
> +
> + if (!ppb)
> + return 0;
> +
> + if (ppb < 0) {
> + neg_adj = 1;
> + ppb = -ppb;
> + }
> + rate = ppb;
> + rate <<= 26;
> + rate = div_u64(rate, 1953125);
> +
> + hi = (rate >> 16) & PTP_RATE_HI_MASK;
> + if (neg_adj)
> + hi |= PTP_RATE_DIR;
> +
> + lo = rate & 0xffff;
> +
> + ext_write(phydev, PAGE4, PTP_RATEH, hi);
> + ext_write(phydev, PAGE4, PTP_RATEL, lo);
> +
> + return 0;
> +}
> +
> +static int ptp_dp83640_adjtime(void *priv, struct timespec *ts)
> +{
> + return tdr_write(priv, ts, PTP_STEP_CLK);
> +}
> +
> +static int ptp_dp83640_gettime(void *priv, struct timespec *ts)
> +{
> + struct dp83640_private *dp83640 = priv;
> + struct phy_device *phydev = dp83640->phydev;
> + unsigned int val[4];
> +
> + spin_lock(&dp83640->ptp_tdr_lock);
> +
> + ext_write(phydev, PAGE4, PTP_CTL, PTP_RD_CLK);
> +
> + val[0] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[15:0] */
> + val[1] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[31:16] */
> + val[2] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[15:0] */
> + val[3] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[31:16] */
> +
> + spin_unlock(&dp83640->ptp_tdr_lock);
> +
> + ts->tv_nsec = val[0] | (val[1] << 16);
> + ts->tv_sec = val[2] | (val[3] << 16);
> +
> + return 0;
> +}
> +
> +static int ptp_dp83640_settime(void *priv, struct timespec *ts)
> +{
> + return tdr_write(priv, ts, PTP_LOAD_CLK);
> +}
> +
> +static int ptp_dp83640_gettimer(void *priv, int index, struct itimerspec *ts)
> +{
> + /* We do not (yet) offer any ancillary features. */
> + return -EOPNOTSUPP;
> +}
> +
> +static int ptp_dp83640_settimer(void *p, int i, int abs, struct itimerspec *ts)
> +{
> + /* We do not (yet) offer any ancillary features. */
> + return -EOPNOTSUPP;
> +}
> +
> +static int ptp_dp83640_enable(void *priv, struct ptp_clock_request *rq, int on)
> +{
> + /* We do not (yet) offer any ancillary features. */
> + return -EOPNOTSUPP;
> +}
> +
> +static struct ptp_clock_info ptp_dp83640_caps = {
> + .owner = THIS_MODULE,
> + .name = "dp83640 timer",
> + .max_adj = 1953124,
> + .n_alarm = 0,
> + .n_ext_ts = 0,
> + .n_per_out = 0,
> + .pps = 0,
> + .priv = NULL,
ditto here, can leave the 0s and nulls out.
> + .adjfreq = ptp_dp83640_adjfreq,
> + .adjtime = ptp_dp83640_adjtime,
> + .gettime = ptp_dp83640_gettime,
> + .settime = ptp_dp83640_settime,
> + .gettimer = ptp_dp83640_gettimer,
> + .settimer = ptp_dp83640_settimer,
> + .enable = ptp_dp83640_enable,
> +};
> +
> +/* time stamping methods */
> +
> static int expired(struct rxts *rxts)
> {
> return time_after(jiffies, rxts->tmo);
> @@ -144,6 +274,7 @@ static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
> static int dp83640_probe(struct phy_device *phydev)
> {
> struct dp83640_private *dp83640;
> + unsigned long flags;
> int i;
>
> if (sk_chk_filter(ptp_filter, ARRAY_SIZE(ptp_filter))) {
> @@ -155,8 +286,9 @@ static int dp83640_probe(struct phy_device *phydev)
> if (!dp83640)
> return -ENOMEM;
>
> + dp83640->phydev = phydev;
> + spin_lock_init(&dp83640->ptp_tdr_lock);
> spin_lock_init(&dp83640->extreg_lock);
> -
> INIT_LIST_HEAD(&dp83640->rxts);
> INIT_LIST_HEAD(&dp83640->pool);
>
> @@ -165,12 +297,36 @@ static int dp83640_probe(struct phy_device *phydev)
>
> phydev->priv = dp83640;
>
> + spin_lock_irqsave(&clock_lock, flags);
> +
> + if (!dp83640_clock) {
> + ptp_dp83640_caps.priv = dp83640;
> + dp83640_clock = ptp_clock_register(&ptp_dp83640_caps);
> + if (IS_ERR(dp83640_clock)) {
> + spin_unlock_irqrestore(&clock_lock, flags);
> + kfree(dp83640);
> + return PTR_ERR(dp83640_clock);
> + }
> + }
> + spin_unlock_irqrestore(&clock_lock, flags);
> +
> return 0;
> }
>
> static void dp83640_remove(struct phy_device *phydev)
> {
> struct dp83640_private *dp83640 = phydev->priv;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&clock_lock, flags);
> +
> + if (ptp_dp83640_caps.priv == dp83640) {
> + ptp_clock_unregister(dp83640_clock);
> + dp83640_clock = NULL;
> + ptp_dp83640_caps.priv = NULL;
> + }
> + spin_unlock_irqrestore(&clock_lock, flags);
> +
> kfree(dp83640);
> }
>
> --
> 1.6.3.3
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* [PATCH net-next 1/4] cnic: Return error code in cnic_cm_close() if unsuccessful.
From: Michael Chan @ 2010-06-15 18:57 UTC (permalink / raw)
To: davem; +Cc: netdev
So that bnx2i can handle the error condition immediately and not have to
wait for timeout.
Signed-off-by: Michael Chan <mchan@broadcom.com.
Signed-off-by: Eddie Wai <waie@broadcom.com>
---
drivers/net/cnic.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/drivers/net/cnic.c b/drivers/net/cnic.c
index 908d89a..b20e11c 100644
--- a/drivers/net/cnic.c
+++ b/drivers/net/cnic.c
@@ -3026,6 +3026,8 @@ static int cnic_cm_close(struct cnic_sock *csk)
if (cnic_close_prep(csk)) {
csk->state = L4_KCQE_OPCODE_VALUE_CLOSE_COMP;
return cnic_cm_close_req(csk);
+ } else {
+ return -EALREADY;
}
return 0;
}
--
1.6.4.GIT
^ permalink raw reply related
* [PATCH net-next 3/4] cnic: Refactor and fix cnic_ready_to_close().
From: Michael Chan @ 2010-06-15 18:57 UTC (permalink / raw)
To: davem; +Cc: netdev
In-Reply-To: <1276628223-27125-2-git-send-email-mchan@broadcom.com>
Combine RESET_RECEIVED and RESET_COMP logic and fix race condition
between these 2 events and cnic_cm_close(). In particular, we need
to (test_and_clear_bit(SK_F_OFFLD_COMPLETE, &csk->flags)) before we
update csk->state.
Signed-off-by: Michael Chan <mchan@broadcom.com>
Signed-off-by: Eddie Wai <waie@broadcom.com>
---
drivers/net/cnic.c | 26 ++++++++++----------------
1 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/drivers/net/cnic.c b/drivers/net/cnic.c
index 48fdbce..11eeded 100644
--- a/drivers/net/cnic.c
+++ b/drivers/net/cnic.c
@@ -3198,25 +3198,19 @@ static int cnic_cm_alloc_mem(struct cnic_dev *dev)
static int cnic_ready_to_close(struct cnic_sock *csk, u32 opcode)
{
- if (opcode == L4_KCQE_OPCODE_VALUE_RESET_RECEIVED) {
- if (test_and_clear_bit(SK_F_OFFLD_COMPLETE, &csk->flags))
- csk->state = opcode;
- }
- if ((opcode == csk->state) ||
- (opcode == L4_KCQE_OPCODE_VALUE_RESET_RECEIVED &&
- csk->state == L4_KCQE_OPCODE_VALUE_CLOSE_COMP)) {
- if (!test_and_set_bit(SK_F_CLOSING, &csk->flags))
- return 1;
+ if (test_and_clear_bit(SK_F_OFFLD_COMPLETE, &csk->flags)) {
+ /* Unsolicited RESET_COMP or RESET_RECEIVED */
+ opcode = L4_KCQE_OPCODE_VALUE_RESET_RECEIVED;
+ csk->state = opcode;
}
- /* 57710+ only workaround to handle unsolicited RESET_COMP
- * which will be treated like a RESET RCVD notification
- * which triggers the clean up procedure
+
+ /* 1. If event opcode matches the expected event in csk->state
+ * 2. If the expected event is CLOSE_COMP, we accept any event
*/
- else if (opcode == L4_KCQE_OPCODE_VALUE_RESET_COMP) {
- if (!test_and_set_bit(SK_F_CLOSING, &csk->flags)) {
- csk->state = L4_KCQE_OPCODE_VALUE_RESET_RECEIVED;
+ if (opcode == csk->state ||
+ csk->state == L4_KCQE_OPCODE_VALUE_CLOSE_COMP) {
+ if (!test_and_set_bit(SK_F_CLOSING, &csk->flags))
return 1;
- }
}
return 0;
}
--
1.6.4.GIT
^ permalink raw reply related
* [PATCH net-next 4/4] cnic: Fix cnic_cm_abort() error handling.
From: Michael Chan @ 2010-06-15 18:57 UTC (permalink / raw)
To: davem; +Cc: netdev
In-Reply-To: <1276628223-27125-3-git-send-email-mchan@broadcom.com>
Fix the code that handles the error case when cnic_cm_abort() cannot
proceed normally. We cannot just set the csk->state and we must
go through cnic_ready_to_close() to handle all the conditions. We
also add error return code in cnic_cm_abort().
Signed-off-by: Michael Chan <mchan@broadcom.com>
Signed-off-by: Eddie Wai <waie@broadcom.com>
---
drivers/net/cnic.c | 29 ++++++++++++++++++-----------
1 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/drivers/net/cnic.c b/drivers/net/cnic.c
index 11eeded..e5539f0 100644
--- a/drivers/net/cnic.c
+++ b/drivers/net/cnic.c
@@ -2996,7 +2996,7 @@ err_out:
static int cnic_cm_abort(struct cnic_sock *csk)
{
struct cnic_local *cp = csk->dev->cnic_priv;
- u32 opcode;
+ u32 opcode = L4_KCQE_OPCODE_VALUE_RESET_COMP;
if (!cnic_in_use(csk))
return -EINVAL;
@@ -3008,12 +3008,9 @@ static int cnic_cm_abort(struct cnic_sock *csk)
* connect was not successful.
*/
- csk->state = L4_KCQE_OPCODE_VALUE_RESET_COMP;
- if (test_bit(SK_F_PG_OFFLD_COMPLETE, &csk->flags))
- opcode = csk->state;
- else
- opcode = L5CM_RAMROD_CMD_ID_TERMINATE_OFFLOAD;
cp->close_conn(csk, opcode);
+ if (csk->state != opcode)
+ return -EALREADY;
return 0;
}
@@ -3206,11 +3203,16 @@ static int cnic_ready_to_close(struct cnic_sock *csk, u32 opcode)
/* 1. If event opcode matches the expected event in csk->state
* 2. If the expected event is CLOSE_COMP, we accept any event
+ * 3. If the expected event is 0, meaning the connection was never
+ * never established, we accept the opcode from cm_abort.
*/
- if (opcode == csk->state ||
- csk->state == L4_KCQE_OPCODE_VALUE_CLOSE_COMP) {
- if (!test_and_set_bit(SK_F_CLOSING, &csk->flags))
+ if (opcode == csk->state || csk->state == 0 ||
+ csk->state == L4_KCQE_OPCODE_VALUE_CLOSE_COMP) {
+ if (!test_and_set_bit(SK_F_CLOSING, &csk->flags)) {
+ if (csk->state == 0)
+ csk->state = opcode;
return 1;
+ }
}
return 0;
}
@@ -3227,6 +3229,7 @@ static void cnic_close_bnx2_conn(struct cnic_sock *csk, u32 opcode)
clear_bit(SK_F_CONNECT_START, &csk->flags);
cnic_close_conn(csk);
+ csk->state = opcode;
cnic_cm_upcall(cp, csk, opcode);
}
@@ -3256,8 +3259,12 @@ static void cnic_close_bnx2x_conn(struct cnic_sock *csk, u32 opcode)
case L4_KCQE_OPCODE_VALUE_RESET_RECEIVED:
case L4_KCQE_OPCODE_VALUE_CLOSE_COMP:
case L4_KCQE_OPCODE_VALUE_RESET_COMP:
- if (cnic_ready_to_close(csk, opcode))
- cmd = L5CM_RAMROD_CMD_ID_SEARCHER_DELETE;
+ if (cnic_ready_to_close(csk, opcode)) {
+ if (test_bit(SK_F_PG_OFFLD_COMPLETE, &csk->flags))
+ cmd = L5CM_RAMROD_CMD_ID_SEARCHER_DELETE;
+ else
+ close_complete = 1;
+ }
break;
case L5CM_RAMROD_CMD_ID_SEARCHER_DELETE:
cmd = L5CM_RAMROD_CMD_ID_TERMINATE_OFFLOAD;
--
1.6.4.GIT
^ permalink raw reply related
* [PATCH net-next 2/4] cnic: Refactor code in cnic_cm_process_kcqe().
From: Michael Chan @ 2010-06-15 18:57 UTC (permalink / raw)
To: davem; +Cc: netdev
In-Reply-To: <1276628223-27125-1-git-send-email-mchan@broadcom.com>
Move chip-specific code to the respective chip's ->close_conn() functions
for better code organization.
Signed-off-by: Michael Chan <mchan@broadcom.com>
Signed-off-by: Eddie Wai <waie@broadcom.com>
---
drivers/net/cnic.c | 15 +++++++++------
1 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/drivers/net/cnic.c b/drivers/net/cnic.c
index b20e11c..48fdbce 100644
--- a/drivers/net/cnic.c
+++ b/drivers/net/cnic.c
@@ -3143,12 +3143,6 @@ static void cnic_cm_process_kcqe(struct cnic_dev *dev, struct kcqe *kcqe)
break;
case L4_KCQE_OPCODE_VALUE_RESET_RECEIVED:
- if (test_bit(CNIC_F_BNX2_CLASS, &dev->flags)) {
- cnic_cm_upcall(cp, csk, opcode);
- break;
- } else if (test_and_clear_bit(SK_F_OFFLD_COMPLETE, &csk->flags))
- csk->state = opcode;
- /* fall through */
case L4_KCQE_OPCODE_VALUE_CLOSE_COMP:
case L4_KCQE_OPCODE_VALUE_RESET_COMP:
case L5CM_RAMROD_CMD_ID_SEARCHER_DELETE:
@@ -3204,6 +3198,10 @@ static int cnic_cm_alloc_mem(struct cnic_dev *dev)
static int cnic_ready_to_close(struct cnic_sock *csk, u32 opcode)
{
+ if (opcode == L4_KCQE_OPCODE_VALUE_RESET_RECEIVED) {
+ if (test_and_clear_bit(SK_F_OFFLD_COMPLETE, &csk->flags))
+ csk->state = opcode;
+ }
if ((opcode == csk->state) ||
(opcode == L4_KCQE_OPCODE_VALUE_RESET_RECEIVED &&
csk->state == L4_KCQE_OPCODE_VALUE_CLOSE_COMP)) {
@@ -3228,6 +3226,11 @@ static void cnic_close_bnx2_conn(struct cnic_sock *csk, u32 opcode)
struct cnic_dev *dev = csk->dev;
struct cnic_local *cp = dev->cnic_priv;
+ if (opcode == L4_KCQE_OPCODE_VALUE_RESET_RECEIVED) {
+ cnic_cm_upcall(cp, csk, opcode);
+ return;
+ }
+
clear_bit(SK_F_CONNECT_START, &csk->flags);
cnic_close_conn(csk);
cnic_cm_upcall(cp, csk, opcode);
--
1.6.4.GIT
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox