* [PATCH V8 01/13] time: Introduce timekeeping_inject_offset
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:12 ` John Stultz
-1 siblings, 0 replies; 76+ messages in thread
From: John Stultz @ 2010-12-31 19:12 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
This adds a kernel-internal timekeeping interface to add or subtract
a fixed amount from CLOCK_REALTIME. This makes it so kernel users or
interfaces trying to do so do not have to read the time, then add an
offset and then call settimeofday(), which adds some extra error in
comparision to just simply adding the offset in the kernel timekeeping
core.
CC: Thomas Gleixner <tglx-hfZtesqFncYOwBW4kG4KsQ@public.gmane.org>
CC: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
Signed-off-by: John Stultz <john.stultz-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
include/linux/time.h | 1 +
kernel/time/timekeeping.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 0 deletions(-)
diff --git a/include/linux/time.h b/include/linux/time.h
index 9f15ac7..b402134 100644
--- a/include/linux/time.h
+++ b/include/linux/time.h
@@ -166,6 +166,7 @@ extern int timekeeping_valid_for_hres(void);
extern u64 timekeeping_max_deferment(void);
extern void update_wall_time(void);
extern void timekeeping_leap_insert(int leapsecond);
+extern int timekeeping_inject_offset(struct timespec *ts);
struct tms;
extern void do_sys_times(struct tms *);
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 49010d8..77e79b3 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -340,6 +340,42 @@ int do_settimeofday(struct timespec *tv)
EXPORT_SYMBOL(do_settimeofday);
+
+/**
+ * timekeeping_inject_offset - Adds or subtracts from the current time.
+ * @tv: pointer to the timespec variable containing the offset
+ *
+ * Adds or subtracts an offset value from the current time.
+ */
+int timekeeping_inject_offset(struct timespec *ts)
+{
+ unsigned long flags;
+
+ if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
+ return -EINVAL;
+
+ write_seqlock_irqsave(&xtime_lock, flags);
+
+ timekeeping_forward_now();
+
+ xtime = timespec_add(xtime, *ts);
+ wall_to_monotonic = timespec_sub(wall_to_monotonic, *ts);
+
+ timekeeper.ntp_error = 0;
+ ntp_clear();
+
+ update_vsyscall(&xtime, &wall_to_monotonic, timekeeper.clock,
+ timekeeper.mult);
+
+ write_sequnlock_irqrestore(&xtime_lock, flags);
+
+ /* signal hrtimers about time change */
+ clock_was_set();
+
+ return 0;
+}
+EXPORT_SYMBOL(timekeeping_inject_offset);
+
/**
* change_clocksource - Swaps clocksources if a new one is available
*
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 01/13] time: Introduce timekeeping_inject_offset
@ 2010-12-31 19:12 ` John Stultz
0 siblings, 0 replies; 76+ messages in thread
From: John Stultz @ 2010-12-31 19:12 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
This adds a kernel-internal timekeeping interface to add or subtract
a fixed amount from CLOCK_REALTIME. This makes it so kernel users or
interfaces trying to do so do not have to read the time, then add an
offset and then call settimeofday(), which adds some extra error in
comparision to just simply adding the offset in the kernel timekeeping
core.
CC: Thomas Gleixner <tglx@linutronix.de>
CC: Richard Cochran <richard.cochran@omicron.at>
Signed-off-by: John Stultz <john.stultz@linaro.org>
---
include/linux/time.h | 1 +
kernel/time/timekeeping.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 0 deletions(-)
diff --git a/include/linux/time.h b/include/linux/time.h
index 9f15ac7..b402134 100644
--- a/include/linux/time.h
+++ b/include/linux/time.h
@@ -166,6 +166,7 @@ extern int timekeeping_valid_for_hres(void);
extern u64 timekeeping_max_deferment(void);
extern void update_wall_time(void);
extern void timekeeping_leap_insert(int leapsecond);
+extern int timekeeping_inject_offset(struct timespec *ts);
struct tms;
extern void do_sys_times(struct tms *);
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 49010d8..77e79b3 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -340,6 +340,42 @@ int do_settimeofday(struct timespec *tv)
EXPORT_SYMBOL(do_settimeofday);
+
+/**
+ * timekeeping_inject_offset - Adds or subtracts from the current time.
+ * @tv: pointer to the timespec variable containing the offset
+ *
+ * Adds or subtracts an offset value from the current time.
+ */
+int timekeeping_inject_offset(struct timespec *ts)
+{
+ unsigned long flags;
+
+ if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
+ return -EINVAL;
+
+ write_seqlock_irqsave(&xtime_lock, flags);
+
+ timekeeping_forward_now();
+
+ xtime = timespec_add(xtime, *ts);
+ wall_to_monotonic = timespec_sub(wall_to_monotonic, *ts);
+
+ timekeeper.ntp_error = 0;
+ ntp_clear();
+
+ update_vsyscall(&xtime, &wall_to_monotonic, timekeeper.clock,
+ timekeeper.mult);
+
+ write_sequnlock_irqrestore(&xtime_lock, flags);
+
+ /* signal hrtimers about time change */
+ clock_was_set();
+
+ return 0;
+}
+EXPORT_SYMBOL(timekeeping_inject_offset);
+
/**
* change_clocksource - Swaps clocksources if a new one is available
*
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread[parent not found: <15a12892b0bfc17327d2b3a7695c81fbe6f34337.1293820862.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>]
* Re: [PATCH V8 01/13] time: Introduce timekeeping_inject_offset
2010-12-31 19:12 ` John Stultz
(?)
@ 2011-01-06 22:00 ` Arnd Bergmann
-1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2011-01-06 22:00 UTC (permalink / raw)
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Christoph Lameter, David Miller, John Stultz,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner, Richard Cochran
On Friday 31 December 2010, John Stultz <richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> This adds a kernel-internal timekeeping interface to add or subtract
Note that this is not the correct way to submit a patch from someone else.
You should instead use your own name and email address as the sender,
and have "From: John Stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>" as the first line in
the body of the email. You also need to add your own "Signed-off-by:
Richard Cochran <richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>" line below the S-o-b from
John.
Using git-send-email plus git-format-patch will gets this right if
the commit was done correctly in your own git tree. If you prefer to
use a different set of tools, you need to watch out for these details
yourself.
Arnd
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 01/13] time: Introduce timekeeping_inject_offset
@ 2011-01-06 22:00 ` Arnd Bergmann
0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2011-01-06 22:00 UTC (permalink / raw)
To: John Stultz
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Christoph Lameter, David Miller, John Stultz,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner, Richard Cochran
On Friday 31 December 2010, John Stultz <richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> This adds a kernel-internal timekeeping interface to add or subtract
Note that this is not the correct way to submit a patch from someone else.
You should instead use your own name and email address as the sender,
and have "From: John Stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>" as the first line in
the body of the email. You also need to add your own "Signed-off-by:
Richard Cochran <richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>" line below the S-o-b from
John.
Using git-send-email plus git-format-patch will gets this right if
the commit was done correctly in your own git tree. If you prefer to
use a different set of tools, you need to watch out for these details
yourself.
Arnd
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 01/13] time: Introduce timekeeping_inject_offset
@ 2011-01-06 22:00 ` Arnd Bergmann
0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2011-01-06 22:00 UTC (permalink / raw)
To: John Stultz
Cc: linux-kernel, linux-api, netdev, Alan Cox, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner, Richard Cochran
On Friday 31 December 2010, John Stultz <richardcochran@gmail.com> wrote:
> This adds a kernel-internal timekeeping interface to add or subtract
Note that this is not the correct way to submit a patch from someone else.
You should instead use your own name and email address as the sender,
and have "From: John Stultz <johnstul@us.ibm.com>" as the first line in
the body of the email. You also need to add your own "Signed-off-by:
Richard Cochran <richardcochran@gmail.com>" line below the S-o-b from
John.
Using git-send-email plus git-format-patch will gets this right if
the commit was done correctly in your own git tree. If you prefer to
use a different set of tools, you need to watch out for these details
yourself.
Arnd
^ permalink raw reply [flat|nested] 76+ messages in thread
* [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:12 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:12 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
This patch adds a new mode bit into the timex structure. When set, the bit
instructs the kernel to add the given time value to the current time.
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
include/linux/timex.h | 3 ++-
kernel/time/ntp.c | 11 +++++++++++
2 files changed, 13 insertions(+), 1 deletions(-)
diff --git a/include/linux/timex.h b/include/linux/timex.h
index 32d852f..82d4b24 100644
--- a/include/linux/timex.h
+++ b/include/linux/timex.h
@@ -73,7 +73,7 @@ struct timex {
long tolerance; /* clock frequency tolerance (ppm)
* (read only)
*/
- struct timeval time; /* (read only) */
+ struct timeval time; /* (read only, except for ADJ_SETOFFSET) */
long tick; /* (modified) usecs between clock ticks */
long ppsfreq; /* pps frequency (scaled ppm) (ro) */
@@ -101,6 +101,7 @@ struct timex {
#define ADJ_ESTERROR 0x0008 /* estimated time error */
#define ADJ_STATUS 0x0010 /* clock status */
#define ADJ_TIMECONST 0x0020 /* pll time constant */
+#define ADJ_SETOFFSET 0x0040 /* add 'time' to current time */
#define ADJ_TAI 0x0080 /* set TAI offset */
#define ADJ_MICRO 0x1000 /* select microsecond resolution */
#define ADJ_NANO 0x2000 /* select nanosecond resolution */
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index d232189..0c87858 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -482,6 +482,17 @@ int do_adjtimex(struct timex *txc)
hrtimer_cancel(&leap_timer);
}
+ if (txc->modes & ADJ_SETOFFSET) {
+ struct timespec delta;
+ if ((unsigned long)txc->time.tv_usec >= NSEC_PER_SEC)
+ return -EINVAL;
+ delta.tv_sec = txc->time.tv_sec;
+ delta.tv_nsec = txc->time.tv_usec;
+ if (!(txc->modes & ADJ_NANO))
+ delta.tv_nsec *= 1000;
+ timekeeping_inject_offset(&delta);
+ }
+
getnstimeofday(&ts);
write_seqlock_irq(&xtime_lock);
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2010-12-31 19:12 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:12 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
This patch adds a new mode bit into the timex structure. When set, the bit
instructs the kernel to add the given time value to the current time.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
include/linux/timex.h | 3 ++-
kernel/time/ntp.c | 11 +++++++++++
2 files changed, 13 insertions(+), 1 deletions(-)
diff --git a/include/linux/timex.h b/include/linux/timex.h
index 32d852f..82d4b24 100644
--- a/include/linux/timex.h
+++ b/include/linux/timex.h
@@ -73,7 +73,7 @@ struct timex {
long tolerance; /* clock frequency tolerance (ppm)
* (read only)
*/
- struct timeval time; /* (read only) */
+ struct timeval time; /* (read only, except for ADJ_SETOFFSET) */
long tick; /* (modified) usecs between clock ticks */
long ppsfreq; /* pps frequency (scaled ppm) (ro) */
@@ -101,6 +101,7 @@ struct timex {
#define ADJ_ESTERROR 0x0008 /* estimated time error */
#define ADJ_STATUS 0x0010 /* clock status */
#define ADJ_TIMECONST 0x0020 /* pll time constant */
+#define ADJ_SETOFFSET 0x0040 /* add 'time' to current time */
#define ADJ_TAI 0x0080 /* set TAI offset */
#define ADJ_MICRO 0x1000 /* select microsecond resolution */
#define ADJ_NANO 0x2000 /* select nanosecond resolution */
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index d232189..0c87858 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -482,6 +482,17 @@ int do_adjtimex(struct timex *txc)
hrtimer_cancel(&leap_timer);
}
+ if (txc->modes & ADJ_SETOFFSET) {
+ struct timespec delta;
+ if ((unsigned long)txc->time.tv_usec >= NSEC_PER_SEC)
+ return -EINVAL;
+ delta.tv_sec = txc->time.tv_sec;
+ delta.tv_nsec = txc->time.tv_usec;
+ if (!(txc->modes & ADJ_NANO))
+ delta.tv_nsec *= 1000;
+ timekeeping_inject_offset(&delta);
+ }
+
getnstimeofday(&ts);
write_seqlock_irq(&xtime_lock);
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread[parent not found: <fe2bec763bec155381d62721aa9da84aeca2785c.1293820862.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2010-12-31 19:12 ` Richard Cochran
@ 2011-01-01 20:38 ` Kuwahara,T.
-1 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-01 20:38 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Sat, Jan 1, 2011 at 4:12 AM, Richard Cochran
<richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> +#define ADJ_SETOFFSET 0x0040 /* add 'time' to current time */
As you know, it conflicts with MOD_PPSMAX. And also, it is logically
the same as ADJ_OFFSET, unless the kernel PLL is enabled explicitly.
So here's my simple solution:
---
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index c631168..d492887 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -119,14 +119,21 @@
return div_s64(offset64 << (NTP_SCALE_SHIFT - SHIFT_FLL), secs);
}
-static void ntp_update_offset(long offset)
+static void ntp_update_offset(long offset, struct timespec *ts)
{
s64 freq_adj;
s64 offset64;
long secs;
- if (!(time_status & STA_PLL))
+ if (!(time_status & STA_PLL)) {
+ offset64 = offset;
+ if (!(time_status & STA_NANO))
+ offset64 *= NSEC_PER_USEC;
+
+ set_normalized_timespec(ts, ts->tv_sec, offset64 + ts->tv_nsec);
+
return;
+ }
if (!(time_status & STA_NANO))
offset *= NSEC_PER_USEC;
@@ -430,7 +437,7 @@
time_tai = txc->constant;
if (txc->modes & ADJ_OFFSET)
- ntp_update_offset(txc->offset);
+ ntp_update_offset(txc->offset, ts);
if (txc->modes & ADJ_TICK)
tick_usec = txc->tick;
@@ -526,6 +533,9 @@
write_sequnlock_irq(&xtime_lock);
+ if ((txc->modes & ADJ_OFFSET) && !(time_status & STA_PLL))
+ do_settimeofday(&ts);
+
txc->time.tv_sec = ts.tv_sec;
txc->time.tv_usec = ts.tv_nsec;
if (!(time_status & STA_NANO))
--
^ permalink raw reply related [flat|nested] 76+ messages in thread* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-01 20:38 ` Kuwahara,T.
0 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-01 20:38 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Sat, Jan 1, 2011 at 4:12 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> +#define ADJ_SETOFFSET 0x0040 /* add 'time' to current time */
As you know, it conflicts with MOD_PPSMAX. And also, it is logically
the same as ADJ_OFFSET, unless the kernel PLL is enabled explicitly.
So here's my simple solution:
---
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index c631168..d492887 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -119,14 +119,21 @@
return div_s64(offset64 << (NTP_SCALE_SHIFT - SHIFT_FLL), secs);
}
-static void ntp_update_offset(long offset)
+static void ntp_update_offset(long offset, struct timespec *ts)
{
s64 freq_adj;
s64 offset64;
long secs;
- if (!(time_status & STA_PLL))
+ if (!(time_status & STA_PLL)) {
+ offset64 = offset;
+ if (!(time_status & STA_NANO))
+ offset64 *= NSEC_PER_USEC;
+
+ set_normalized_timespec(ts, ts->tv_sec, offset64 + ts->tv_nsec);
+
return;
+ }
if (!(time_status & STA_NANO))
offset *= NSEC_PER_USEC;
@@ -430,7 +437,7 @@
time_tai = txc->constant;
if (txc->modes & ADJ_OFFSET)
- ntp_update_offset(txc->offset);
+ ntp_update_offset(txc->offset, ts);
if (txc->modes & ADJ_TICK)
tick_usec = txc->tick;
@@ -526,6 +533,9 @@
write_sequnlock_irq(&xtime_lock);
+ if ((txc->modes & ADJ_OFFSET) && !(time_status & STA_PLL))
+ do_settimeofday(&ts);
+
txc->time.tv_sec = ts.tv_sec;
txc->time.tv_usec = ts.tv_nsec;
if (!(time_status & STA_NANO))
--
^ permalink raw reply related [flat|nested] 76+ messages in thread[parent not found: <AANLkTini2WdT-1v4k9V3JOZYDkA59P+SyscTe8-fK2Wk-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-01 20:38 ` Kuwahara,T.
@ 2011-01-08 17:50 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-08 17:50 UTC (permalink / raw)
To: Kuwahara,T.
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Sun, Jan 02, 2011 at 05:38:19AM +0900, Kuwahara,T. wrote:
> As you know, it conflicts with MOD_PPSMAX. And also, it is logically
> the same as ADJ_OFFSET, unless the kernel PLL is enabled explicitly.
I choose another bit, for the next version of the patch series.
>
> So here's my simple solution:
>
I have read the API documentation in the nanokernel source archive. It
explains the (very complex looking) timex structure quite clearly and
nicely. Now I am more convinced than ever that adding a new mode bit
is the best way to go, as opposed to ADJ_OFFSET/!STA_PLL, because:
1. The mode bits update kernel variables. That is what we want.
2. Clearing STA_PLL means disable adjustments.
3. The range of the timex.offset is way too small.
I expand on each point, below. BTW, the API document is also available
for online reading here:
http://www.slac.stanford.edu/comp/unix/package/rtems/src/ssrlApps/ntpNanoclock/api.htm
1. Looking at the API, the documentation for the bits of the 'modes'
field states:
These bits control which field of the timex structure are used
to update the corresponding kernel variable. The bits may be
set in any combination. See the description below for which
bits control which variable.
With the ADJ_SETOFFSET mode, we are telling the kernel to update
the instantaneous value of the 'current time' variable. That usage
agrees with the sematics of the other mode bits.
2. The documentation for STA_PLL states:
Master enable switch for the PLL/FLL loop. The algorithm is
responsive to time and/or frequency updates if set; otherwise,
no change in the current time or frequency will be made other
than to complete a pending phase adjustment. This bit does not
affect the PPS loop.
So when we clear this bit, the kernel promises that it will make
"no change in the current time." The proposed ADJ_OFFSET/!STA_PLL
solution would break this pattern.
3. The timex.offset field is of type "long" and represents either
nanoseconds or microseconds. On 32 bit architectures, the maximum
possible adjustment would be
2^31 * 10^-6 = 2147.5 seconds
which is less than one hour. For the first adjustment of a clock,
we want to be able to jump the clock arbitrarily. Not every
computer has an RTC, and so some boot up believing that it is still
the year 1970.
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-08 17:50 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-08 17:50 UTC (permalink / raw)
To: Kuwahara,T.
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Sun, Jan 02, 2011 at 05:38:19AM +0900, Kuwahara,T. wrote:
> As you know, it conflicts with MOD_PPSMAX. And also, it is logically
> the same as ADJ_OFFSET, unless the kernel PLL is enabled explicitly.
I choose another bit, for the next version of the patch series.
>
> So here's my simple solution:
>
I have read the API documentation in the nanokernel source archive. It
explains the (very complex looking) timex structure quite clearly and
nicely. Now I am more convinced than ever that adding a new mode bit
is the best way to go, as opposed to ADJ_OFFSET/!STA_PLL, because:
1. The mode bits update kernel variables. That is what we want.
2. Clearing STA_PLL means disable adjustments.
3. The range of the timex.offset is way too small.
I expand on each point, below. BTW, the API document is also available
for online reading here:
http://www.slac.stanford.edu/comp/unix/package/rtems/src/ssrlApps/ntpNanoclock/api.htm
1. Looking at the API, the documentation for the bits of the 'modes'
field states:
These bits control which field of the timex structure are used
to update the corresponding kernel variable. The bits may be
set in any combination. See the description below for which
bits control which variable.
With the ADJ_SETOFFSET mode, we are telling the kernel to update
the instantaneous value of the 'current time' variable. That usage
agrees with the sematics of the other mode bits.
2. The documentation for STA_PLL states:
Master enable switch for the PLL/FLL loop. The algorithm is
responsive to time and/or frequency updates if set; otherwise,
no change in the current time or frequency will be made other
than to complete a pending phase adjustment. This bit does not
affect the PPS loop.
So when we clear this bit, the kernel promises that it will make
"no change in the current time." The proposed ADJ_OFFSET/!STA_PLL
solution would break this pattern.
3. The timex.offset field is of type "long" and represents either
nanoseconds or microseconds. On 32 bit architectures, the maximum
possible adjustment would be
2^31 * 10^-6 = 2147.5 seconds
which is less than one hour. For the first adjustment of a clock,
we want to be able to jump the clock arbitrarily. Not every
computer has an RTC, and so some boot up believing that it is still
the year 1970.
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread[parent not found: <20110108175028.GA22308-7KxsofuKt4IfAd9E5cN8NEzG7cXyKsk/@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-08 17:50 ` Richard Cochran
@ 2011-01-09 21:07 ` Kuwahara,T.
-1 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-09 21:07 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Sun, Jan 9, 2011 at 2:50 AM, Richard Cochran
<richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> we want to be able to jump the clock arbitrarily.
Another problem remains: How do you deal with leap seconds? I mean,
given that 1 minute is not always 60 seconds, then what time was it
XXXXX seconds ago? Maybe some kind of lookup table is necessary, but
in such case, isn't it a better choice just to use the
clock_settime/settimeofday syscall?
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-09 21:07 ` Kuwahara,T.
0 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-09 21:07 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Sun, Jan 9, 2011 at 2:50 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> we want to be able to jump the clock arbitrarily.
Another problem remains: How do you deal with leap seconds? I mean,
given that 1 minute is not always 60 seconds, then what time was it
XXXXX seconds ago? Maybe some kind of lookup table is necessary, but
in such case, isn't it a better choice just to use the
clock_settime/settimeofday syscall?
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-09 21:07 ` Kuwahara,T.
(?)
@ 2011-01-10 7:22 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-10 7:22 UTC (permalink / raw)
To: Kuwahara,T.
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Mon, Jan 10, 2011 at 06:07:26AM +0900, Kuwahara,T. wrote:
>
> Another problem remains: How do you deal with leap seconds? I mean,
Since you have changed the topic, does that mean that you now agree
with adding a new mode bit? ;)
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
[parent not found: <AANLkTikigXGSACF6R6kfNHyKZ7GFWrUrCMxygvL3fUC6-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-09 21:07 ` Kuwahara,T.
@ 2011-01-10 7:17 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-10 7:17 UTC (permalink / raw)
To: Kuwahara,T.
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Mon, Jan 10, 2011 at 06:07:26AM +0900, Kuwahara,T. wrote:
> On Sun, Jan 9, 2011 at 2:50 AM, Richard Cochran
> <richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> > we want to be able to jump the clock arbitrarily.
>
> Another problem remains: How do you deal with leap seconds? I mean,
> given that 1 minute is not always 60 seconds, then what time was it
> XXXXX seconds ago? Maybe some kind of lookup table is necessary, but
> in such case, isn't it a better choice just to use the
> clock_settime/settimeofday syscall?
Well, first of all, the PTP Hardware Clocks for which this whole patch
set was created in the first place will keep their time as TAI. Adding
seconds to such time values is unambiguous.
Secondly, the question you ask applies equally to the existing
interfaces, so it is a mute point with regard to the patch series.
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-10 7:17 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-10 7:17 UTC (permalink / raw)
To: Kuwahara,T.
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Mon, Jan 10, 2011 at 06:07:26AM +0900, Kuwahara,T. wrote:
> On Sun, Jan 9, 2011 at 2:50 AM, Richard Cochran
> <richardcochran@gmail.com> wrote:
> > we want to be able to jump the clock arbitrarily.
>
> Another problem remains: How do you deal with leap seconds? I mean,
> given that 1 minute is not always 60 seconds, then what time was it
> XXXXX seconds ago? Maybe some kind of lookup table is necessary, but
> in such case, isn't it a better choice just to use the
> clock_settime/settimeofday syscall?
Well, first of all, the PTP Hardware Clocks for which this whole patch
set was created in the first place will keep their time as TAI. Adding
seconds to such time values is unambiguous.
Secondly, the question you ask applies equally to the existing
interfaces, so it is a mute point with regard to the patch series.
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
[parent not found: <20110110071759.GA4864-7KxsofuKt4IfAd9E5cN8NEzG7cXyKsk/@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-10 7:17 ` Richard Cochran
@ 2011-01-10 20:47 ` Kuwahara,T.
-1 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-10 20:47 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Mon, Jan 10, 2011 at 4:17 PM, Richard Cochran
<richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> the PTP Hardware Clocks for which this whole patch
> set was created in the first place will keep their time as TAI.
Are you sure of that? I don't have the standard handy (it's non-free,
right?) but it seems that the Annex B states differently. But that's
not the point anyway. My concern is that your patch not only adds the
useless (and broken) feature to the existing syscall but also makes a
permanent change to the public interface for your own use. That's
what I'm against. So if you stop touching the struct timex, I won't
complain anymore.
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-10 20:47 ` Kuwahara,T.
0 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-10 20:47 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Mon, Jan 10, 2011 at 4:17 PM, Richard Cochran
<richardcochran@gmail.com> wrote:
> the PTP Hardware Clocks for which this whole patch
> set was created in the first place will keep their time as TAI.
Are you sure of that? I don't have the standard handy (it's non-free,
right?) but it seems that the Annex B states differently. But that's
not the point anyway. My concern is that your patch not only adds the
useless (and broken) feature to the existing syscall but also makes a
permanent change to the public interface for your own use. That's
what I'm against. So if you stop touching the struct timex, I won't
complain anymore.
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-10 20:47 ` Kuwahara,T.
(?)
@ 2011-01-10 21:11 ` john stultz
-1 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-10 21:11 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Tue, 2011-01-11 at 05:47 +0900, Kuwahara,T. wrote:
> On Mon, Jan 10, 2011 at 4:17 PM, Richard Cochran
> <richardcochran@gmail.com> wrote:
> > the PTP Hardware Clocks for which this whole patch
> > set was created in the first place will keep their time as TAI.
>
> Are you sure of that? I don't have the standard handy (it's non-free,
> right?) but it seems that the Annex B states differently. But that's
> not the point anyway. My concern is that your patch not only adds the
> useless (and broken) feature to the existing syscall but also makes a
> permanent change to the public interface for your own use. That's
> what I'm against. So if you stop touching the struct timex, I won't
> complain anymore.
You still haven't explained *why* you're so protective of the timex and
adjtimex interfaces. While I do want to keep compatible the
functionality where possible, I don't see why Linux should be limited by
what other OSes do.
Injecting an offset to the system time seems like a reasonable thing for
adjtimex to do (rather then adding a new syscall). Further utilizing a
new mode bit for this functionality seems reasonable and cleaner then
your suggestions for utilizing existing mode bits in combined with other
magic bits. If there is a compelling reason why not to do this, do
please let us know! We might just agree with you after hearing it. :)
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-10 20:47 ` Kuwahara,T.
(?)
(?)
@ 2011-01-11 11:09 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-11 11:09 UTC (permalink / raw)
To: Kuwahara,T.
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Tue, Jan 11, 2011 at 05:47:13AM +0900, Kuwahara,T. wrote:
> On Mon, Jan 10, 2011 at 4:17 PM, Richard Cochran
> <richardcochran@gmail.com> wrote:
> > the PTP Hardware Clocks for which this whole patch
> > set was created in the first place will keep their time as TAI.
>
> Are you sure of that?
Yes.
> I don't have the standard handy (it's non-free, right?) but it seems
> that the Annex B states differently.
IEEE Std 1588-2008, Page 41:
The timescale PTP: In normal operation, the epoch is the PTP
epoch and the timescale is continuous; see 7.2.4. The unit of
measure of time is the SI second as realized on the rotating
geoid.
IEEE Std 1588-2008, Page 42:
The PTP epoch is 1 January 1970 00:00:00 TAI, which is 31
December 1969 23:59:51.999918 UTC.
The standard does permit using some other, arbitrary timescale, "set
by an administrative procedure." But no other timescales are
standardized by 1588.
> But that's not the point anyway. My concern is that your patch not
> only adds the useless (and broken) feature to the existing syscall
> but also makes a permanent change to the public interface for your
> own use.
It is neither useless or broken. Using the new interface, you can
estimate offset and frequency for a few seconds and jump directly into
a closely synchronized state (like within 100 nanoseconds) relative to
another clock on the network. That is pretty useful.
Your issue with leap seconds is really a non-issue. The time value of
the ADJ_SETOFFSET mode is a time interval, *not* an absolute time.
> That's what I'm against. So if you stop touching the struct timex,
> I won't complain anymore.
I have no love for the timex interface. However, I used it for the new
syscall because Arnd Bergmann and John Stultz specifically asked for
it. Also, several other people have reviewed the new call without
objecting to the new mode.
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-09 21:07 ` Kuwahara,T.
@ 2011-01-10 16:49 ` john stultz
-1 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-10 16:49 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Mon, 2011-01-10 at 06:07 +0900, Kuwahara,T. wrote:
> On Sun, Jan 9, 2011 at 2:50 AM, Richard Cochran
> <richardcochran-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> > we want to be able to jump the clock arbitrarily.
>
> Another problem remains: How do you deal with leap seconds? I mean,
> given that 1 minute is not always 60 seconds, then what time was it
> XXXXX seconds ago? Maybe some kind of lookup table is necessary, but
> in such case, isn't it a better choice just to use the
> clock_settime/settimeofday syscall?
Leapsecond processing is done via an absolute hrtimer. Thus when the
time offset is set, the hrtimers that should have expired will fire
(just like with settimeofday) and the adjustment will then be made.
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-10 16:49 ` john stultz
0 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-10 16:49 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Mon, 2011-01-10 at 06:07 +0900, Kuwahara,T. wrote:
> On Sun, Jan 9, 2011 at 2:50 AM, Richard Cochran
> <richardcochran@gmail.com> wrote:
> > we want to be able to jump the clock arbitrarily.
>
> Another problem remains: How do you deal with leap seconds? I mean,
> given that 1 minute is not always 60 seconds, then what time was it
> XXXXX seconds ago? Maybe some kind of lookup table is necessary, but
> in such case, isn't it a better choice just to use the
> clock_settime/settimeofday syscall?
Leapsecond processing is done via an absolute hrtimer. Thus when the
time offset is set, the hrtimers that should have expired will fire
(just like with settimeofday) and the adjustment will then be made.
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-10 16:49 ` john stultz
@ 2011-01-10 20:45 ` Kuwahara,T.
-1 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-10 20:45 UTC (permalink / raw)
To: john stultz
Cc: Richard Cochran, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org> wrote:
> Leapsecond processing is done via an absolute hrtimer. Thus when the
> time offset is set, the hrtimers that should have expired will fire
> (just like with settimeofday) and the adjustment will then be made.
How do you convert relative time to absolute time? It's not trivial
because TAI offset is also a variable.
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-10 20:45 ` Kuwahara,T.
0 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-10 20:45 UTC (permalink / raw)
To: john stultz
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul@us.ibm.com> wrote:
> Leapsecond processing is done via an absolute hrtimer. Thus when the
> time offset is set, the hrtimers that should have expired will fire
> (just like with settimeofday) and the adjustment will then be made.
How do you convert relative time to absolute time? It's not trivial
because TAI offset is also a variable.
^ permalink raw reply [flat|nested] 76+ messages in thread
[parent not found: <AANLkTikdg4Pb5c4wcCU3d3Eku=mpiW_TEkBz0A2VPdVp-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-10 20:45 ` Kuwahara,T.
@ 2011-01-10 21:06 ` john stultz
-1 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-10 21:06 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Tue, 2011-01-11 at 05:45 +0900, Kuwahara,T. wrote:
> On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org> wrote:
> > Leapsecond processing is done via an absolute hrtimer. Thus when the
> > time offset is set, the hrtimers that should have expired will fire
> > (just like with settimeofday) and the adjustment will then be made.
>
> How do you convert relative time to absolute time? It's not trivial
> because TAI offset is also a variable.
I don't believe I understand what you're getting at.
The proposed interface is almost identical in functionality to a
userland application doing the following:
offset = my_calculate_offset();
clock_gettime(CLOCK_REALTIME, &now);
newtime = my_add_ts(now, offset);
settimeofday(&newtime, 0);
The only difference is that you avoid the error from the delay between
the gettime call and the settime call. It just adds the offset directly
to the CLOCK_REALTIME.
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-10 21:06 ` john stultz
0 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-10 21:06 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Tue, 2011-01-11 at 05:45 +0900, Kuwahara,T. wrote:
> On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul@us.ibm.com> wrote:
> > Leapsecond processing is done via an absolute hrtimer. Thus when the
> > time offset is set, the hrtimers that should have expired will fire
> > (just like with settimeofday) and the adjustment will then be made.
>
> How do you convert relative time to absolute time? It's not trivial
> because TAI offset is also a variable.
I don't believe I understand what you're getting at.
The proposed interface is almost identical in functionality to a
userland application doing the following:
offset = my_calculate_offset();
clock_gettime(CLOCK_REALTIME, &now);
newtime = my_add_ts(now, offset);
settimeofday(&newtime, 0);
The only difference is that you avoid the error from the delay between
the gettime call and the settime call. It just adds the offset directly
to the CLOCK_REALTIME.
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-10 21:06 ` john stultz
@ 2011-01-11 20:32 ` Kuwahara,T.
-1 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-11 20:32 UTC (permalink / raw)
To: john stultz
Cc: Richard Cochran, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Tue, Jan 11, 2011 at 6:06 AM, john stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org> wrote:
> On Tue, 2011-01-11 at 05:45 +0900, Kuwahara,T. wrote:
>> On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org> wrote:
>> > Leapsecond processing is done via an absolute hrtimer. Thus when the
>> > time offset is set, the hrtimers that should have expired will fire
>> > (just like with settimeofday) and the adjustment will then be made.
>>
>> How do you convert relative time to absolute time? It's not trivial
>> because TAI offset is also a variable.
>
> I don't believe I understand what you're getting at.
>
> The proposed interface is almost identical in functionality to a
> userland application doing the following:
>
> offset = my_calculate_offset();
> clock_gettime(CLOCK_REALTIME, &now);
> newtime = my_add_ts(now, offset);
> settimeofday(&newtime, 0);
>
> The only difference is that you avoid the error from the delay between
> the gettime call and the settime call. It just adds the offset directly
> to the CLOCK_REALTIME.
For example, what time was it 3 seconds after 2008-12-31 23:59:59?
You may say, of course it's 2009-01-01 00:00:02. But it's not true.
You wonder why? Because a leap second had been added at midnight.
unix time UTC offset
---------- -------- ---
1230767997 23:59:57 -2
1230767998 23:59:58 -1
1230767999 23:59:59 0
1230768000 23:59:60 1 (leap second)
1230768000 00:00:00 2
1230768001 00:00:01 3
1230768002 00:00:02 4
That's why I said it's not trivial, and that your patch is broken and
thus useless. Unfortunately, there's no remedy for this as long as a
nonlinear timescale such as the unix time is being used, since the
leap second insertion/deletion is a non-deterministic event.
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-11 20:32 ` Kuwahara,T.
0 siblings, 0 replies; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-11 20:32 UTC (permalink / raw)
To: john stultz
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Tue, Jan 11, 2011 at 6:06 AM, john stultz <johnstul@us.ibm.com> wrote:
> On Tue, 2011-01-11 at 05:45 +0900, Kuwahara,T. wrote:
>> On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul@us.ibm.com> wrote:
>> > Leapsecond processing is done via an absolute hrtimer. Thus when the
>> > time offset is set, the hrtimers that should have expired will fire
>> > (just like with settimeofday) and the adjustment will then be made.
>>
>> How do you convert relative time to absolute time? It's not trivial
>> because TAI offset is also a variable.
>
> I don't believe I understand what you're getting at.
>
> The proposed interface is almost identical in functionality to a
> userland application doing the following:
>
> offset = my_calculate_offset();
> clock_gettime(CLOCK_REALTIME, &now);
> newtime = my_add_ts(now, offset);
> settimeofday(&newtime, 0);
>
> The only difference is that you avoid the error from the delay between
> the gettime call and the settime call. It just adds the offset directly
> to the CLOCK_REALTIME.
For example, what time was it 3 seconds after 2008-12-31 23:59:59?
You may say, of course it's 2009-01-01 00:00:02. But it's not true.
You wonder why? Because a leap second had been added at midnight.
unix time UTC offset
---------- -------- ---
1230767997 23:59:57 -2
1230767998 23:59:58 -1
1230767999 23:59:59 0
1230768000 23:59:60 1 (leap second)
1230768000 00:00:00 2
1230768001 00:00:01 3
1230768002 00:00:02 4
That's why I said it's not trivial, and that your patch is broken and
thus useless. Unfortunately, there's no remedy for this as long as a
nonlinear timescale such as the unix time is being used, since the
leap second insertion/deletion is a non-deterministic event.
^ permalink raw reply [flat|nested] 76+ messages in thread
[parent not found: <AANLkTik+2udoJ6JyZMhkd1yhr2W9hSWO_vD_zdm+BoOG-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-11 20:32 ` Kuwahara,T.
@ 2011-01-11 20:55 ` john stultz
-1 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-11 20:55 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Wed, 2011-01-12 at 05:32 +0900, Kuwahara,T. wrote:
> On Tue, Jan 11, 2011 at 6:06 AM, john stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org> wrote:
> > On Tue, 2011-01-11 at 05:45 +0900, Kuwahara,T. wrote:
> >> On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org> wrote:
> >> > Leapsecond processing is done via an absolute hrtimer. Thus when the
> >> > time offset is set, the hrtimers that should have expired will fire
> >> > (just like with settimeofday) and the adjustment will then be made.
> >>
> >> How do you convert relative time to absolute time? It's not trivial
> >> because TAI offset is also a variable.
> >
> > I don't believe I understand what you're getting at.
> >
> > The proposed interface is almost identical in functionality to a
> > userland application doing the following:
> >
> > offset = my_calculate_offset();
> > clock_gettime(CLOCK_REALTIME, &now);
> > newtime = my_add_ts(now, offset);
> > settimeofday(&newtime, 0);
> >
> > The only difference is that you avoid the error from the delay between
> > the gettime call and the settime call. It just adds the offset directly
> > to the CLOCK_REALTIME.
>
> For example, what time was it 3 seconds after 2008-12-31 23:59:59?
> You may say, of course it's 2009-01-01 00:00:02. But it's not true.
> You wonder why? Because a leap second had been added at midnight.
>
> unix time UTC offset
> ---------- -------- ---
> 1230767997 23:59:57 -2
> 1230767998 23:59:58 -1
> 1230767999 23:59:59 0
> 1230768000 23:59:60 1 (leap second)
> 1230768000 00:00:00 2
> 1230768001 00:00:01 3
> 1230768002 00:00:02 4
>
> That's why I said it's not trivial, and that your patch is broken and
> thus useless.
So the kernel handles leap second insertion via a timer. Thus at the end
of 23:59:59, it will inject a leapsecond by setting the time back by one
second (back to 23:59:59) and setting the TIME_OOP flag.
This timer is an absolute timer, so if someone moves the clock forward
across the 23:59:59 boundary, the adjustment will still be made.
The patch is not broken, nor useless.
> Unfortunately, there's no remedy for this as long as a
> nonlinear timescale such as the unix time is being used, since the
> leap second insertion/deletion is a non-deterministic event.
Indeed. Leapseconds in unix time are frustrating to deal with, and
complicates applications having to be careful with them. But we do
manage them properly and applications can detect that they are pending
(via checking for TIME_INS) or if they are in effect (TIME_OOP).
Richards patch is in no different from settimeofday() solutions for
correcting for an offset, with the exception of the fact that it does
not introduce error due to delay beteween any gettime and settime call.
If your complaint is that leapseconds are ugly to deal with in unix
time, then I agree. However, I'm at a total loss for understanding your
passionate objections to Richard's patch.
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
@ 2011-01-11 20:55 ` john stultz
0 siblings, 0 replies; 76+ messages in thread
From: john stultz @ 2011-01-11 20:55 UTC (permalink / raw)
To: Kuwahara,T.
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Wed, 2011-01-12 at 05:32 +0900, Kuwahara,T. wrote:
> On Tue, Jan 11, 2011 at 6:06 AM, john stultz <johnstul@us.ibm.com> wrote:
> > On Tue, 2011-01-11 at 05:45 +0900, Kuwahara,T. wrote:
> >> On Tue, Jan 11, 2011 at 1:49 AM, john stultz <johnstul@us.ibm.com> wrote:
> >> > Leapsecond processing is done via an absolute hrtimer. Thus when the
> >> > time offset is set, the hrtimers that should have expired will fire
> >> > (just like with settimeofday) and the adjustment will then be made.
> >>
> >> How do you convert relative time to absolute time? It's not trivial
> >> because TAI offset is also a variable.
> >
> > I don't believe I understand what you're getting at.
> >
> > The proposed interface is almost identical in functionality to a
> > userland application doing the following:
> >
> > offset = my_calculate_offset();
> > clock_gettime(CLOCK_REALTIME, &now);
> > newtime = my_add_ts(now, offset);
> > settimeofday(&newtime, 0);
> >
> > The only difference is that you avoid the error from the delay between
> > the gettime call and the settime call. It just adds the offset directly
> > to the CLOCK_REALTIME.
>
> For example, what time was it 3 seconds after 2008-12-31 23:59:59?
> You may say, of course it's 2009-01-01 00:00:02. But it's not true.
> You wonder why? Because a leap second had been added at midnight.
>
> unix time UTC offset
> ---------- -------- ---
> 1230767997 23:59:57 -2
> 1230767998 23:59:58 -1
> 1230767999 23:59:59 0
> 1230768000 23:59:60 1 (leap second)
> 1230768000 00:00:00 2
> 1230768001 00:00:01 3
> 1230768002 00:00:02 4
>
> That's why I said it's not trivial, and that your patch is broken and
> thus useless.
So the kernel handles leap second insertion via a timer. Thus at the end
of 23:59:59, it will inject a leapsecond by setting the time back by one
second (back to 23:59:59) and setting the TIME_OOP flag.
This timer is an absolute timer, so if someone moves the clock forward
across the 23:59:59 boundary, the adjustment will still be made.
The patch is not broken, nor useless.
> Unfortunately, there's no remedy for this as long as a
> nonlinear timescale such as the unix time is being used, since the
> leap second insertion/deletion is a non-deterministic event.
Indeed. Leapseconds in unix time are frustrating to deal with, and
complicates applications having to be careful with them. But we do
manage them properly and applications can detect that they are pending
(via checking for TIME_INS) or if they are in effect (TIME_OOP).
Richards patch is in no different from settimeofday() solutions for
correcting for an offset, with the exception of the fact that it does
not introduce error due to delay beteween any gettime and settime call.
If your complaint is that leapseconds are ugly to deal with in unix
time, then I agree. However, I'm at a total loss for understanding your
passionate objections to Richard's patch.
thanks
-john
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 02/13] ntp: add ADJ_SETOFFSET mode bit
2011-01-11 20:55 ` john stultz
(?)
@ 2011-01-12 20:39 ` Kuwahara,T.
[not found] ` <AANLkTikYuYwb4BLsU3BF_=d9fAdcfb0AC2itDBeyFsNq-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
-1 siblings, 1 reply; 76+ messages in thread
From: Kuwahara,T. @ 2011-01-12 20:39 UTC (permalink / raw)
To: john stultz
Cc: Richard Cochran, linux-kernel, linux-api, netdev, Alan Cox,
Arnd Bergmann, Christoph Lameter, David Miller, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti, Thomas Gleixner
On Wed, Jan 12, 2011 at 5:55 AM, john stultz <johnstul@us.ibm.com> wrote:
> So the kernel handles leap second insertion via a timer. Thus at the end
> of 23:59:59, it will inject a leapsecond by setting the time back by one
> second (back to 23:59:59) and setting the TIME_OOP flag.
>
> This timer is an absolute timer, so if someone moves the clock forward
> across the 23:59:59 boundary, the adjustment will still be made.
>
> The patch is not broken, nor useless.
It takes into account only one upcoming leap second, but ignores all
the others. That's not sufficient for arbitrary adjustments.
^ permalink raw reply [flat|nested] 76+ messages in thread
* [PATCH V8 04/13] posix_clocks: add clock_adjtime for arm
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:13 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:13 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
This patch adds the clock_adjtime system call to the arm architecture.
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
arch/arm/include/asm/unistd.h | 1 +
arch/arm/kernel/calls.S | 1 +
2 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/arch/arm/include/asm/unistd.h b/arch/arm/include/asm/unistd.h
index c891eb7..f58d881 100644
--- a/arch/arm/include/asm/unistd.h
+++ b/arch/arm/include/asm/unistd.h
@@ -396,6 +396,7 @@
#define __NR_fanotify_init (__NR_SYSCALL_BASE+367)
#define __NR_fanotify_mark (__NR_SYSCALL_BASE+368)
#define __NR_prlimit64 (__NR_SYSCALL_BASE+369)
+#define __NR_clock_adjtime (__NR_SYSCALL_BASE+370)
/*
* The following SWIs are ARM private.
diff --git a/arch/arm/kernel/calls.S b/arch/arm/kernel/calls.S
index 5c26ecc..430de4c 100644
--- a/arch/arm/kernel/calls.S
+++ b/arch/arm/kernel/calls.S
@@ -379,6 +379,7 @@
CALL(sys_fanotify_init)
CALL(sys_fanotify_mark)
CALL(sys_prlimit64)
+/* 370 */ CALL(sys_clock_adjtime)
#ifndef syscalls_counted
.equ syscalls_padding, ((NR_syscalls + 3) & ~3) - NR_syscalls
#define syscalls_counted
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread
* [PATCH V8 04/13] posix_clocks: add clock_adjtime for arm
@ 2010-12-31 19:13 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:13 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
This patch adds the clock_adjtime system call to the arm architecture.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
arch/arm/include/asm/unistd.h | 1 +
arch/arm/kernel/calls.S | 1 +
2 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/arch/arm/include/asm/unistd.h b/arch/arm/include/asm/unistd.h
index c891eb7..f58d881 100644
--- a/arch/arm/include/asm/unistd.h
+++ b/arch/arm/include/asm/unistd.h
@@ -396,6 +396,7 @@
#define __NR_fanotify_init (__NR_SYSCALL_BASE+367)
#define __NR_fanotify_mark (__NR_SYSCALL_BASE+368)
#define __NR_prlimit64 (__NR_SYSCALL_BASE+369)
+#define __NR_clock_adjtime (__NR_SYSCALL_BASE+370)
/*
* The following SWIs are ARM private.
diff --git a/arch/arm/kernel/calls.S b/arch/arm/kernel/calls.S
index 5c26ecc..430de4c 100644
--- a/arch/arm/kernel/calls.S
+++ b/arch/arm/kernel/calls.S
@@ -379,6 +379,7 @@
CALL(sys_fanotify_init)
CALL(sys_fanotify_mark)
CALL(sys_prlimit64)
+/* 370 */ CALL(sys_clock_adjtime)
#ifndef syscalls_counted
.equ syscalls_padding, ((NR_syscalls + 3) & ~3) - NR_syscalls
#define syscalls_counted
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread
* [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:15 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:15 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
Paraphrasing tglx:
This patch simplifies and clarifies the code, doing the normal thing
with function pointer structures. Stuff which is not implemented does
not magically become called via some common function. There is no
point in doing that.
We fill in the various k_clock structs with the correct pointers in
the first place and let the NULL case return a sensible error
value. The data structure does not become larger that way. It's a
little bit more init code, but that's fine if we make the code better
in general. In that case it's not even more init code, it's just
filling the data structures which we register.
My own words:
For now, each of the registered k_clocks has the previously NULL
functions assigned to the common_xyz function, since that usage was
implicitly enforced by the CLOCK_DISPTACH macro. These functions are
marked with a /* default: */ comment.
As as possible further improvement for the future, one could go
through and replace the various no_xyz function pointers in the
k_clocks with NULL pointers.
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
include/linux/posix-timers.h | 14 +++
include/linux/time.h | 2 +
kernel/posix-timers.c | 241 ++++++++++++++++++++++++++++++++++--------
3 files changed, 213 insertions(+), 44 deletions(-)
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index b05d9b8..eef7f9c 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -18,6 +18,18 @@ struct cpu_timer_list {
int firing;
};
+/* Bit fields within a clockid:
+ *
+ * The most significant 29 bits hold either a pid or a file descriptor.
+ *
+ * Bit 2 indicates whether a cpu clock refers to a thread or a process.
+ *
+ * Bits 1 and 0 give the type: PROF=0, VIRT=1, SCHED=2, or FD=3.
+ *
+ * A clockid is invalid if bits 2, 1, and 0 all set (see also CLOCK_INVALID
+ * in include/linux/time.h)
+ */
+
#define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))
#define CPUCLOCK_PERTHREAD(clock) \
(((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0)
@@ -29,6 +41,8 @@ struct cpu_timer_list {
#define CPUCLOCK_VIRT 1
#define CPUCLOCK_SCHED 2
#define CPUCLOCK_MAX 3
+#define CLOCKFD CPUCLOCK_MAX
+#define CLOCKFD_MASK (CPUCLOCK_PERTHREAD_MASK|CPUCLOCK_CLOCK_MASK)
#define MAKE_PROCESS_CPUCLOCK(pid, clock) \
((~(clockid_t) (pid) << 3) | (clockid_t) (clock))
diff --git a/include/linux/time.h b/include/linux/time.h
index b402134..99d4b14 100644
--- a/include/linux/time.h
+++ b/include/linux/time.h
@@ -300,6 +300,8 @@ struct itimerval {
#define CLOCKS_MASK (CLOCK_REALTIME | CLOCK_MONOTONIC)
#define CLOCKS_MONO CLOCK_MONOTONIC
+#define CLOCK_INVALID -1
+
/*
* The various flags for setting POSIX.1b interval timers:
*/
diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c
index 502bde4..65b0599 100644
--- a/kernel/posix-timers.c
+++ b/kernel/posix-timers.c
@@ -138,6 +138,7 @@ static struct k_clock posix_clocks[MAX_CLOCKS];
*/
static int common_nsleep(const clockid_t, int flags, struct timespec *t,
struct timespec __user *rmtp);
+static long common_nsleep_restart(struct restart_block *restart_block);
static void common_timer_get(struct k_itimer *, struct itimerspec *);
static int common_timer_set(struct k_itimer *, int,
struct itimerspec *, struct itimerspec *);
@@ -152,41 +153,14 @@ static inline void unlock_timer(struct k_itimer *timr, unsigned long flags)
spin_unlock_irqrestore(&timr->it_lock, flags);
}
-/*
- * Call the k_clock hook function if non-null, or the default function.
- */
-#define CLOCK_DISPATCH(clock, call, arglist) \
- ((clock) < 0 ? posix_cpu_##call arglist : \
- (posix_clocks[clock].call != NULL \
- ? (*posix_clocks[clock].call) arglist : common_##call arglist))
-
-/*
- * Default clock hook functions when the struct k_clock passed
- * to register_posix_clock leaves a function pointer null.
- *
- * The function common_CALL is the default implementation for
- * the function pointer CALL in struct k_clock.
- */
-static inline int common_clock_getres(const clockid_t which_clock,
- struct timespec *tp)
-{
- tp->tv_sec = 0;
- tp->tv_nsec = posix_clocks[which_clock].res;
- return 0;
-}
-
-/*
- * Get real time for posix timers
- */
static int common_clock_get(clockid_t which_clock, struct timespec *tp)
{
ktime_get_real_ts(tp);
return 0;
}
-static inline int common_clock_set(const clockid_t which_clock,
- struct timespec *tp)
+static int common_clock_set(const clockid_t which_clock, struct timespec *tp)
{
return do_sys_settimeofday(tp, NULL);
}
@@ -197,7 +171,7 @@ static int common_timer_create(struct k_itimer *new_timer)
return 0;
}
-static inline int common_clock_adj(const clockid_t which_clock, struct timex *t)
+static int common_clock_adj(const clockid_t which_clock, struct timex *t)
{
return do_adjtimex(t);
}
@@ -214,19 +188,14 @@ static int no_nsleep(const clockid_t which_clock, int flags,
}
/*
- * Return nonzero if we know a priori this clockid_t value is bogus.
+ * Return 'true' if we know a priori this clockid_t value is bogus.
*/
-static inline int invalid_clockid(const clockid_t which_clock)
+static inline bool invalid_clockid(const clockid_t which_clock)
{
- if (which_clock < 0) /* CPU clock, posix_cpu_* will check it */
- return 0;
- if ((unsigned) which_clock >= MAX_CLOCKS)
- return 1;
- if (posix_clocks[which_clock].clock_getres != NULL)
- return 0;
- if (posix_clocks[which_clock].res != 0)
- return 0;
- return 1;
+ if (which_clock >= MAX_CLOCKS)
+ return true;
+ else
+ return false;
}
/*
@@ -273,12 +242,29 @@ static __init int init_posix_timers(void)
{
struct k_clock clock_realtime = {
.clock_getres = hrtimer_get_res,
+ /* defaults: */
+ .clock_adj = common_clock_adj,
+ .clock_get = common_clock_get,
+ .clock_set = common_clock_set,
+ .nsleep = common_nsleep,
+ .nsleep_restart = common_nsleep_restart,
+ .timer_create = common_timer_create,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_monotonic = {
.clock_getres = hrtimer_get_res,
.clock_get = posix_ktime_get_ts,
.clock_set = do_posix_clock_nosettime,
.clock_adj = do_posix_clock_noadjtime,
+ /* defaults: */
+ .nsleep = common_nsleep,
+ .nsleep_restart = common_nsleep_restart,
+ .timer_create = common_timer_create,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_monotonic_raw = {
.clock_getres = hrtimer_get_res,
@@ -287,6 +273,11 @@ static __init int init_posix_timers(void)
.clock_adj = do_posix_clock_noadjtime,
.timer_create = no_timer_create,
.nsleep = no_nsleep,
+ /* defaults: */
+ .nsleep_restart = common_nsleep_restart,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_realtime_coarse = {
.clock_getres = posix_get_coarse_res,
@@ -295,6 +286,11 @@ static __init int init_posix_timers(void)
.clock_adj = do_posix_clock_noadjtime,
.timer_create = no_timer_create,
.nsleep = no_nsleep,
+ /* defaults: */
+ .nsleep_restart = common_nsleep_restart,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_monotonic_coarse = {
.clock_getres = posix_get_coarse_res,
@@ -303,6 +299,11 @@ static __init int init_posix_timers(void)
.clock_adj = do_posix_clock_noadjtime,
.timer_create = no_timer_create,
.nsleep = no_nsleep,
+ /* defaults: */
+ .nsleep_restart = common_nsleep_restart,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
register_posix_clock(CLOCK_REALTIME, &clock_realtime);
@@ -526,6 +527,160 @@ static void release_posix_timer(struct k_itimer *tmr, int it_id_set)
kmem_cache_free(posix_timers_cache, tmr);
}
+static inline bool clock_is_posix_cpu(const clockid_t id)
+{
+ if ((id & CLOCKFD_MASK) == CLOCKFD)
+ return false;
+ else
+ return true;
+}
+
+static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_getres ?
+ posix_clocks[id].clock_getres(id, ts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_getres(id, ts);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_clock_set(const clockid_t id, struct timespec *ts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_set ?
+ posix_clocks[id].clock_set(id, ts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_set(id, ts);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_clock_get(const clockid_t id, struct timespec *ts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_get ?
+ posix_clocks[id].clock_get(id, ts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_get(id, ts);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_clock_adj(const clockid_t id, struct timex *tx)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_adj ?
+ posix_clocks[id].clock_adj(id, tx) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_adj(id, tx);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_timer_create(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0) {
+ return posix_clocks[id].timer_create ?
+ posix_clocks[id].timer_create(kit) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_timer_create(kit);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_nsleep(const clockid_t id, int flags,
+ struct timespec *ts,
+ struct timespec __user *rts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].nsleep ?
+ posix_clocks[id].nsleep(id, flags, ts, rts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_nsleep(id, flags, ts, rts);
+
+ return -EINVAL;
+}
+
+static inline long dispatch_nsleep_restart(struct restart_block *block)
+{
+ clockid_t id = block->arg0;
+
+ if (id >= 0) {
+ return posix_clocks[id].nsleep_restart ?
+ posix_clocks[id].nsleep_restart(block) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_nsleep_restart(block);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_timer_set(struct k_itimer *kit, int flags,
+ struct itimerspec *tsp,
+ struct itimerspec *old)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0) {
+ return posix_clocks[id].timer_set ?
+ posix_clocks[id].timer_set(kit, flags, tsp, old) :
+ -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_timer_set(kit, flags, tsp, old);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_timer_del(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0) {
+ return posix_clocks[id].timer_del ?
+ posix_clocks[id].timer_del(kit) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_timer_del(kit);
+
+ return -EINVAL;
+}
+
+static inline void dispatch_timer_get(struct k_itimer *kit,
+ struct itimerspec *tsp)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0 && posix_clocks[id].timer_get)
+
+ posix_clocks[id].timer_get(kit, tsp);
+
+ else if (clock_is_posix_cpu(id))
+
+ posix_cpu_timer_get(kit, tsp);
+}
+
+#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
+
/* Create a POSIX.1b interval timer. */
SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock,
@@ -847,7 +1002,7 @@ retry:
return error;
}
-static inline int common_timer_del(struct k_itimer *timer)
+static int common_timer_del(struct k_itimer *timer)
{
timer->it.real.interval.tv64 = 0;
@@ -1056,7 +1211,7 @@ SYSCALL_DEFINE4(clock_nanosleep, const clockid_t, which_clock, int, flags,
/*
* nanosleep_restart for monotonic and realtime clocks
*/
-static int common_nsleep_restart(struct restart_block *restart_block)
+static long common_nsleep_restart(struct restart_block *restart_block)
{
return hrtimer_nanosleep_restart(restart_block);
}
@@ -1068,8 +1223,6 @@ static int common_nsleep_restart(struct restart_block *restart_block)
long
clock_nanosleep_restart(struct restart_block *restart_block)
{
- clockid_t which_clock = restart_block->arg0;
-
return CLOCK_DISPATCH(which_clock, nsleep_restart,
(restart_block));
}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2010-12-31 19:15 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:15 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
Paraphrasing tglx:
This patch simplifies and clarifies the code, doing the normal thing
with function pointer structures. Stuff which is not implemented does
not magically become called via some common function. There is no
point in doing that.
We fill in the various k_clock structs with the correct pointers in
the first place and let the NULL case return a sensible error
value. The data structure does not become larger that way. It's a
little bit more init code, but that's fine if we make the code better
in general. In that case it's not even more init code, it's just
filling the data structures which we register.
My own words:
For now, each of the registered k_clocks has the previously NULL
functions assigned to the common_xyz function, since that usage was
implicitly enforced by the CLOCK_DISPTACH macro. These functions are
marked with a /* default: */ comment.
As as possible further improvement for the future, one could go
through and replace the various no_xyz function pointers in the
k_clocks with NULL pointers.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
include/linux/posix-timers.h | 14 +++
include/linux/time.h | 2 +
kernel/posix-timers.c | 241 ++++++++++++++++++++++++++++++++++--------
3 files changed, 213 insertions(+), 44 deletions(-)
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index b05d9b8..eef7f9c 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -18,6 +18,18 @@ struct cpu_timer_list {
int firing;
};
+/* Bit fields within a clockid:
+ *
+ * The most significant 29 bits hold either a pid or a file descriptor.
+ *
+ * Bit 2 indicates whether a cpu clock refers to a thread or a process.
+ *
+ * Bits 1 and 0 give the type: PROF=0, VIRT=1, SCHED=2, or FD=3.
+ *
+ * A clockid is invalid if bits 2, 1, and 0 all set (see also CLOCK_INVALID
+ * in include/linux/time.h)
+ */
+
#define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))
#define CPUCLOCK_PERTHREAD(clock) \
(((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0)
@@ -29,6 +41,8 @@ struct cpu_timer_list {
#define CPUCLOCK_VIRT 1
#define CPUCLOCK_SCHED 2
#define CPUCLOCK_MAX 3
+#define CLOCKFD CPUCLOCK_MAX
+#define CLOCKFD_MASK (CPUCLOCK_PERTHREAD_MASK|CPUCLOCK_CLOCK_MASK)
#define MAKE_PROCESS_CPUCLOCK(pid, clock) \
((~(clockid_t) (pid) << 3) | (clockid_t) (clock))
diff --git a/include/linux/time.h b/include/linux/time.h
index b402134..99d4b14 100644
--- a/include/linux/time.h
+++ b/include/linux/time.h
@@ -300,6 +300,8 @@ struct itimerval {
#define CLOCKS_MASK (CLOCK_REALTIME | CLOCK_MONOTONIC)
#define CLOCKS_MONO CLOCK_MONOTONIC
+#define CLOCK_INVALID -1
+
/*
* The various flags for setting POSIX.1b interval timers:
*/
diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c
index 502bde4..65b0599 100644
--- a/kernel/posix-timers.c
+++ b/kernel/posix-timers.c
@@ -138,6 +138,7 @@ static struct k_clock posix_clocks[MAX_CLOCKS];
*/
static int common_nsleep(const clockid_t, int flags, struct timespec *t,
struct timespec __user *rmtp);
+static long common_nsleep_restart(struct restart_block *restart_block);
static void common_timer_get(struct k_itimer *, struct itimerspec *);
static int common_timer_set(struct k_itimer *, int,
struct itimerspec *, struct itimerspec *);
@@ -152,41 +153,14 @@ static inline void unlock_timer(struct k_itimer *timr, unsigned long flags)
spin_unlock_irqrestore(&timr->it_lock, flags);
}
-/*
- * Call the k_clock hook function if non-null, or the default function.
- */
-#define CLOCK_DISPATCH(clock, call, arglist) \
- ((clock) < 0 ? posix_cpu_##call arglist : \
- (posix_clocks[clock].call != NULL \
- ? (*posix_clocks[clock].call) arglist : common_##call arglist))
-
-/*
- * Default clock hook functions when the struct k_clock passed
- * to register_posix_clock leaves a function pointer null.
- *
- * The function common_CALL is the default implementation for
- * the function pointer CALL in struct k_clock.
- */
-static inline int common_clock_getres(const clockid_t which_clock,
- struct timespec *tp)
-{
- tp->tv_sec = 0;
- tp->tv_nsec = posix_clocks[which_clock].res;
- return 0;
-}
-
-/*
- * Get real time for posix timers
- */
static int common_clock_get(clockid_t which_clock, struct timespec *tp)
{
ktime_get_real_ts(tp);
return 0;
}
-static inline int common_clock_set(const clockid_t which_clock,
- struct timespec *tp)
+static int common_clock_set(const clockid_t which_clock, struct timespec *tp)
{
return do_sys_settimeofday(tp, NULL);
}
@@ -197,7 +171,7 @@ static int common_timer_create(struct k_itimer *new_timer)
return 0;
}
-static inline int common_clock_adj(const clockid_t which_clock, struct timex *t)
+static int common_clock_adj(const clockid_t which_clock, struct timex *t)
{
return do_adjtimex(t);
}
@@ -214,19 +188,14 @@ static int no_nsleep(const clockid_t which_clock, int flags,
}
/*
- * Return nonzero if we know a priori this clockid_t value is bogus.
+ * Return 'true' if we know a priori this clockid_t value is bogus.
*/
-static inline int invalid_clockid(const clockid_t which_clock)
+static inline bool invalid_clockid(const clockid_t which_clock)
{
- if (which_clock < 0) /* CPU clock, posix_cpu_* will check it */
- return 0;
- if ((unsigned) which_clock >= MAX_CLOCKS)
- return 1;
- if (posix_clocks[which_clock].clock_getres != NULL)
- return 0;
- if (posix_clocks[which_clock].res != 0)
- return 0;
- return 1;
+ if (which_clock >= MAX_CLOCKS)
+ return true;
+ else
+ return false;
}
/*
@@ -273,12 +242,29 @@ static __init int init_posix_timers(void)
{
struct k_clock clock_realtime = {
.clock_getres = hrtimer_get_res,
+ /* defaults: */
+ .clock_adj = common_clock_adj,
+ .clock_get = common_clock_get,
+ .clock_set = common_clock_set,
+ .nsleep = common_nsleep,
+ .nsleep_restart = common_nsleep_restart,
+ .timer_create = common_timer_create,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_monotonic = {
.clock_getres = hrtimer_get_res,
.clock_get = posix_ktime_get_ts,
.clock_set = do_posix_clock_nosettime,
.clock_adj = do_posix_clock_noadjtime,
+ /* defaults: */
+ .nsleep = common_nsleep,
+ .nsleep_restart = common_nsleep_restart,
+ .timer_create = common_timer_create,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_monotonic_raw = {
.clock_getres = hrtimer_get_res,
@@ -287,6 +273,11 @@ static __init int init_posix_timers(void)
.clock_adj = do_posix_clock_noadjtime,
.timer_create = no_timer_create,
.nsleep = no_nsleep,
+ /* defaults: */
+ .nsleep_restart = common_nsleep_restart,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_realtime_coarse = {
.clock_getres = posix_get_coarse_res,
@@ -295,6 +286,11 @@ static __init int init_posix_timers(void)
.clock_adj = do_posix_clock_noadjtime,
.timer_create = no_timer_create,
.nsleep = no_nsleep,
+ /* defaults: */
+ .nsleep_restart = common_nsleep_restart,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
struct k_clock clock_monotonic_coarse = {
.clock_getres = posix_get_coarse_res,
@@ -303,6 +299,11 @@ static __init int init_posix_timers(void)
.clock_adj = do_posix_clock_noadjtime,
.timer_create = no_timer_create,
.nsleep = no_nsleep,
+ /* defaults: */
+ .nsleep_restart = common_nsleep_restart,
+ .timer_del = common_timer_del,
+ .timer_get = common_timer_get,
+ .timer_set = common_timer_set,
};
register_posix_clock(CLOCK_REALTIME, &clock_realtime);
@@ -526,6 +527,160 @@ static void release_posix_timer(struct k_itimer *tmr, int it_id_set)
kmem_cache_free(posix_timers_cache, tmr);
}
+static inline bool clock_is_posix_cpu(const clockid_t id)
+{
+ if ((id & CLOCKFD_MASK) == CLOCKFD)
+ return false;
+ else
+ return true;
+}
+
+static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_getres ?
+ posix_clocks[id].clock_getres(id, ts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_getres(id, ts);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_clock_set(const clockid_t id, struct timespec *ts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_set ?
+ posix_clocks[id].clock_set(id, ts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_set(id, ts);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_clock_get(const clockid_t id, struct timespec *ts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_get ?
+ posix_clocks[id].clock_get(id, ts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_get(id, ts);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_clock_adj(const clockid_t id, struct timex *tx)
+{
+ if (id >= 0) {
+ return posix_clocks[id].clock_adj ?
+ posix_clocks[id].clock_adj(id, tx) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_clock_adj(id, tx);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_timer_create(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0) {
+ return posix_clocks[id].timer_create ?
+ posix_clocks[id].timer_create(kit) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_timer_create(kit);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_nsleep(const clockid_t id, int flags,
+ struct timespec *ts,
+ struct timespec __user *rts)
+{
+ if (id >= 0) {
+ return posix_clocks[id].nsleep ?
+ posix_clocks[id].nsleep(id, flags, ts, rts) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_nsleep(id, flags, ts, rts);
+
+ return -EINVAL;
+}
+
+static inline long dispatch_nsleep_restart(struct restart_block *block)
+{
+ clockid_t id = block->arg0;
+
+ if (id >= 0) {
+ return posix_clocks[id].nsleep_restart ?
+ posix_clocks[id].nsleep_restart(block) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_nsleep_restart(block);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_timer_set(struct k_itimer *kit, int flags,
+ struct itimerspec *tsp,
+ struct itimerspec *old)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0) {
+ return posix_clocks[id].timer_set ?
+ posix_clocks[id].timer_set(kit, flags, tsp, old) :
+ -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_timer_set(kit, flags, tsp, old);
+
+ return -EINVAL;
+}
+
+static inline int dispatch_timer_del(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0) {
+ return posix_clocks[id].timer_del ?
+ posix_clocks[id].timer_del(kit) : -EINVAL;
+ }
+
+ if (clock_is_posix_cpu(id))
+ return posix_cpu_timer_del(kit);
+
+ return -EINVAL;
+}
+
+static inline void dispatch_timer_get(struct k_itimer *kit,
+ struct itimerspec *tsp)
+{
+ clockid_t id = kit->it_clock;
+
+ if (id >= 0 && posix_clocks[id].timer_get)
+
+ posix_clocks[id].timer_get(kit, tsp);
+
+ else if (clock_is_posix_cpu(id))
+
+ posix_cpu_timer_get(kit, tsp);
+}
+
+#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
+
/* Create a POSIX.1b interval timer. */
SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock,
@@ -847,7 +1002,7 @@ retry:
return error;
}
-static inline int common_timer_del(struct k_itimer *timer)
+static int common_timer_del(struct k_itimer *timer)
{
timer->it.real.interval.tv64 = 0;
@@ -1056,7 +1211,7 @@ SYSCALL_DEFINE4(clock_nanosleep, const clockid_t, which_clock, int, flags,
/*
* nanosleep_restart for monotonic and realtime clocks
*/
-static int common_nsleep_restart(struct restart_block *restart_block)
+static long common_nsleep_restart(struct restart_block *restart_block)
{
return hrtimer_nanosleep_restart(restart_block);
}
@@ -1068,8 +1223,6 @@ static int common_nsleep_restart(struct restart_block *restart_block)
long
clock_nanosleep_restart(struct restart_block *restart_block)
{
- clockid_t which_clock = restart_block->arg0;
-
return CLOCK_DISPATCH(which_clock, nsleep_restart,
(restart_block));
}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2010-12-31 19:15 ` Richard Cochran
(?)
@ 2011-01-03 9:29 ` Peter Zijlstra
2011-01-03 11:51 ` Richard Cochran
-1 siblings, 1 reply; 76+ messages in thread
From: Peter Zijlstra @ 2011-01-03 9:29 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Rodolfo Giometti, Thomas Gleixner
On Fri, 2010-12-31 at 20:15 +0100, Richard Cochran wrote:
> +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
How about you run something like:
:% s/CLOCK_DISPATCH([^,]*, \([^,]*\), \([^)]*)\))/dispatch_\1\2/g
and remove that cruft all together?
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2011-01-03 9:29 ` Peter Zijlstra
@ 2011-01-03 11:51 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-03 11:51 UTC (permalink / raw)
To: Peter Zijlstra
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Rodolfo Giometti, Thomas Gleixner
On Mon, Jan 03, 2011 at 10:29:09AM +0100, Peter Zijlstra wrote:
> On Fri, 2010-12-31 at 20:15 +0100, Richard Cochran wrote:
> > +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
>
> How about you run something like:
>
> :% s/CLOCK_DISPATCH([^,]*, \([^,]*\), \([^)]*)\))/dispatch_\1\2/g
>
> and remove that cruft all together?
Gladly ;^)
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2011-01-03 11:51 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-03 11:51 UTC (permalink / raw)
To: Peter Zijlstra
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Rodolfo Giometti, Thomas Gleixner
On Mon, Jan 03, 2011 at 10:29:09AM +0100, Peter Zijlstra wrote:
> On Fri, 2010-12-31 at 20:15 +0100, Richard Cochran wrote:
> > +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
>
> How about you run something like:
>
> :% s/CLOCK_DISPATCH([^,]*, \([^,]*\), \([^)]*)\))/dispatch_\1\2/g
>
> and remove that cruft all together?
Gladly ;^)
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
[parent not found: <503cd1fa268867573001cfc9bb5681ee3b5b32fa.1293820862.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>]
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2010-12-31 19:15 ` Richard Cochran
@ 2011-01-11 12:57 ` Thomas Gleixner
-1 siblings, 0 replies; 76+ messages in thread
From: Thomas Gleixner @ 2011-01-11 12:57 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti
On Fri, 31 Dec 2010, Richard Cochran wrote:
> +static inline bool clock_is_posix_cpu(const clockid_t id)
> +{
> + if ((id & CLOCKFD_MASK) == CLOCKFD)
> + return false;
> + else
> + return true;
> +}
> +
> +static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
> +{
> + if (id >= 0) {
> + return posix_clocks[id].clock_getres ?
> + posix_clocks[id].clock_getres(id, ts) : -EINVAL;
> + }
> +
> + if (clock_is_posix_cpu(id))
> + return posix_cpu_clock_getres(id, ts);
I wonder whether we should be a bit more clever here and create an
extra entry for posix_cpu_timers in the posix_clocks array and do the
following:
/* These are local to posix-timer.c and not exposed to anything else */
#define POSIX_INV_CLOCK_ID MAX_CLOCKS
#define POSIX_CPU_CLOCK_ID (MAX_CLOCKS + 1)
#define NR_CLOCK_ENTRIES (MAX_CLOCKS + 2)
static struct k_clock posix_clocks[NR_CLOCK_ENTRIES];
static __init int init_posix_timers(void)
{
......
/*
* We leave the POSIX_INV_CLOCK_ID entries zeroed out, so the
* the dispatch code will return -EINVAL.
*/
init_posix_cpu_timer_entries();
}
static clockid_t clock_get_array_id(const clockid_t id)
{
if (id >= 0)
return id < MAX_CLOCKS ? id : POSIX_INV_CLOCK_ID;
if (clock_is_posix_cpu(id))
return POSIX_CPU_CLOCK_ID;
return POSIX_INV_CLOCK_ID;
}
static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
{
struct k_clock *clk = &posix_clocks[clock_get_array_id(id)];
return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
}
That reduces the code significantly and the MAX_CLOCKS check in
clock_get_array_id() replaces the invalid_clock() check in the
syscalls as well. It does not matter whether we check this before or
after copying stuff from user.
Adding your new stuff requires just another entry in the array, the
setup of the function pointers and the CLOCKFD check in
clock_get_array_id(). Everything else just falls in place.
> +
> +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
> +
Can we get rid of this completely please ?
> clock_nanosleep_restart(struct restart_block *restart_block)
> {
> - clockid_t which_clock = restart_block->arg0;
> -
How does that compile ?
> return CLOCK_DISPATCH(which_clock, nsleep_restart,
> (restart_block));
> }
Thanks,
tglx
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2011-01-11 12:57 ` Thomas Gleixner
0 siblings, 0 replies; 76+ messages in thread
From: Thomas Gleixner @ 2011-01-11 12:57 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti
On Fri, 31 Dec 2010, Richard Cochran wrote:
> +static inline bool clock_is_posix_cpu(const clockid_t id)
> +{
> + if ((id & CLOCKFD_MASK) == CLOCKFD)
> + return false;
> + else
> + return true;
> +}
> +
> +static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
> +{
> + if (id >= 0) {
> + return posix_clocks[id].clock_getres ?
> + posix_clocks[id].clock_getres(id, ts) : -EINVAL;
> + }
> +
> + if (clock_is_posix_cpu(id))
> + return posix_cpu_clock_getres(id, ts);
I wonder whether we should be a bit more clever here and create an
extra entry for posix_cpu_timers in the posix_clocks array and do the
following:
/* These are local to posix-timer.c and not exposed to anything else */
#define POSIX_INV_CLOCK_ID MAX_CLOCKS
#define POSIX_CPU_CLOCK_ID (MAX_CLOCKS + 1)
#define NR_CLOCK_ENTRIES (MAX_CLOCKS + 2)
static struct k_clock posix_clocks[NR_CLOCK_ENTRIES];
static __init int init_posix_timers(void)
{
......
/*
* We leave the POSIX_INV_CLOCK_ID entries zeroed out, so the
* the dispatch code will return -EINVAL.
*/
init_posix_cpu_timer_entries();
}
static clockid_t clock_get_array_id(const clockid_t id)
{
if (id >= 0)
return id < MAX_CLOCKS ? id : POSIX_INV_CLOCK_ID;
if (clock_is_posix_cpu(id))
return POSIX_CPU_CLOCK_ID;
return POSIX_INV_CLOCK_ID;
}
static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
{
struct k_clock *clk = &posix_clocks[clock_get_array_id(id)];
return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
}
That reduces the code significantly and the MAX_CLOCKS check in
clock_get_array_id() replaces the invalid_clock() check in the
syscalls as well. It does not matter whether we check this before or
after copying stuff from user.
Adding your new stuff requires just another entry in the array, the
setup of the function pointers and the CLOCKFD check in
clock_get_array_id(). Everything else just falls in place.
> +
> +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
> +
Can we get rid of this completely please ?
> clock_nanosleep_restart(struct restart_block *restart_block)
> {
> - clockid_t which_clock = restart_block->arg0;
> -
How does that compile ?
> return CLOCK_DISPATCH(which_clock, nsleep_restart,
> (restart_block));
> }
Thanks,
tglx
^ permalink raw reply [flat|nested] 76+ messages in thread[parent not found: <alpine.LFD.2.00.1101111224280.12146-bi+AKbBUZKagILUCTcTcHdKyNwTtLsGr@public.gmane.org>]
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2011-01-11 12:57 ` Thomas Gleixner
@ 2011-01-12 7:37 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-12 7:37 UTC (permalink / raw)
To: Thomas Gleixner
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti
On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
> I wonder whether we should be a bit more clever here and create an
> extra entry for posix_cpu_timers in the posix_clocks array and do the
> following:
...
> That reduces the code significantly and the MAX_CLOCKS check in
> clock_get_array_id() replaces the invalid_clock() check in the
> syscalls as well. It does not matter whether we check this before or
> after copying stuff from user.
Well, this does reduce the number of LOC, but the number of
comparisons is the same. I think the code size would be also no
different.
> Adding your new stuff requires just another entry in the array, the
> setup of the function pointers and the CLOCKFD check in
> clock_get_array_id(). Everything else just falls in place.
For me, I am not sure if either version is really very pretty or easy
to understand.
My instinct is to keep the posix_cpu_X and pc_X functions out of the
array of static clock id functions, if only to make the distinction
between the legacy static ids and new dynamic ids clear.
The conclusion from the "dynamic clock as file" discussion was that we
don't want to add any more static clock ids, and new clocks should use
the new, dynamic clock API. So, we should discourage any future
attempt to add to that function array!
Having said that, if you insist on it, I won't mind reworking the
dispatch code as you suggested.
> > +
> > +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
> > +
>
> Can we get rid of this completely please ?
Yes, gladly.
> > clock_nanosleep_restart(struct restart_block *restart_block)
> > {
> > - clockid_t which_clock = restart_block->arg0;
> > -
>
> How does that compile ?
Because the CLOCK_DISPATCH macro, above, makes no use of the first
argument! I have removed the macro for the next round.
> > return CLOCK_DISPATCH(which_clock, nsleep_restart,
> > (restart_block));
> > }
Thanks,
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2011-01-12 7:37 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-12 7:37 UTC (permalink / raw)
To: Thomas Gleixner
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti
On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
> I wonder whether we should be a bit more clever here and create an
> extra entry for posix_cpu_timers in the posix_clocks array and do the
> following:
...
> That reduces the code significantly and the MAX_CLOCKS check in
> clock_get_array_id() replaces the invalid_clock() check in the
> syscalls as well. It does not matter whether we check this before or
> after copying stuff from user.
Well, this does reduce the number of LOC, but the number of
comparisons is the same. I think the code size would be also no
different.
> Adding your new stuff requires just another entry in the array, the
> setup of the function pointers and the CLOCKFD check in
> clock_get_array_id(). Everything else just falls in place.
For me, I am not sure if either version is really very pretty or easy
to understand.
My instinct is to keep the posix_cpu_X and pc_X functions out of the
array of static clock id functions, if only to make the distinction
between the legacy static ids and new dynamic ids clear.
The conclusion from the "dynamic clock as file" discussion was that we
don't want to add any more static clock ids, and new clocks should use
the new, dynamic clock API. So, we should discourage any future
attempt to add to that function array!
Having said that, if you insist on it, I won't mind reworking the
dispatch code as you suggested.
> > +
> > +#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
> > +
>
> Can we get rid of this completely please ?
Yes, gladly.
> > clock_nanosleep_restart(struct restart_block *restart_block)
> > {
> > - clockid_t which_clock = restart_block->arg0;
> > -
>
> How does that compile ?
Because the CLOCK_DISPATCH macro, above, makes no use of the first
argument! I have removed the macro for the next round.
> > return CLOCK_DISPATCH(which_clock, nsleep_restart,
> > (restart_block));
> > }
Thanks,
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread[parent not found: <20110112073728.GA2935-7KxsofuKt4IfAd9E5cN8NEzG7cXyKsk/@public.gmane.org>]
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2011-01-12 7:37 ` Richard Cochran
@ 2011-01-12 11:16 ` Thomas Gleixner
-1 siblings, 0 replies; 76+ messages in thread
From: Thomas Gleixner @ 2011-01-12 11:16 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti
On Wed, 12 Jan 2011, Richard Cochran wrote:
> On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
> > I wonder whether we should be a bit more clever here and create an
> > extra entry for posix_cpu_timers in the posix_clocks array and do the
> > following:
> ...
> > That reduces the code significantly and the MAX_CLOCKS check in
> > clock_get_array_id() replaces the invalid_clock() check in the
> > syscalls as well. It does not matter whether we check this before or
> > after copying stuff from user.
>
> Well, this does reduce the number of LOC, but the number of
> comparisons is the same. I think the code size would be also no
> different.
Right, It's about lines of code and ever repeated if / else constructs
in the dispatch functions. The number of comparisons is probably the
same, but I'm sure that the binary size will be smaller.
We probably can remove the dispatch inlines that way completely and do
the call directly from the syscall function.
> > Adding your new stuff requires just another entry in the array, the
> > setup of the function pointers and the CLOCKFD check in
> > clock_get_array_id(). Everything else just falls in place.
>
> For me, I am not sure if either version is really very pretty or easy
> to understand.
Well, if we document clock_get_array_id() proper, then it's easier to
follow than 10 dispatch functions which have all the same checks and
comparisons inside.
> My instinct is to keep the posix_cpu_X and pc_X functions out of the
> array of static clock id functions, if only to make the distinction
> between the legacy static ids and new dynamic ids clear.
>
> The conclusion from the "dynamic clock as file" discussion was that we
> don't want to add any more static clock ids, and new clocks should use
> the new, dynamic clock API. So, we should discourage any future
> attempt to add to that function array!
These IDs are not public, they are helpers to make the code simpler,
nothing else. I agree, that we don't want to extend the static ids for
public consumption, but implementation wise we can do that confined to
posix-timers.c.
> Having said that, if you insist on it, I won't mind reworking the
> dispatch code as you suggested.
I'm not insisting. I just saw the repeated if/else constructs and
added the clockfd check mentally :) That's where I started to wonder
about a way to do the same thing with way less lines of code.
> > > clock_nanosleep_restart(struct restart_block *restart_block)
> > > {
> > > - clockid_t which_clock = restart_block->arg0;
> > > -
> >
> > How does that compile ?
>
> Because the CLOCK_DISPATCH macro, above, makes no use of the first
Missed that, sorry.
> argument! I have removed the macro for the next round.
Cool.
Thanks,
tglx
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2011-01-12 11:16 ` Thomas Gleixner
0 siblings, 0 replies; 76+ messages in thread
From: Thomas Gleixner @ 2011-01-12 11:16 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti
On Wed, 12 Jan 2011, Richard Cochran wrote:
> On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
> > I wonder whether we should be a bit more clever here and create an
> > extra entry for posix_cpu_timers in the posix_clocks array and do the
> > following:
> ...
> > That reduces the code significantly and the MAX_CLOCKS check in
> > clock_get_array_id() replaces the invalid_clock() check in the
> > syscalls as well. It does not matter whether we check this before or
> > after copying stuff from user.
>
> Well, this does reduce the number of LOC, but the number of
> comparisons is the same. I think the code size would be also no
> different.
Right, It's about lines of code and ever repeated if / else constructs
in the dispatch functions. The number of comparisons is probably the
same, but I'm sure that the binary size will be smaller.
We probably can remove the dispatch inlines that way completely and do
the call directly from the syscall function.
> > Adding your new stuff requires just another entry in the array, the
> > setup of the function pointers and the CLOCKFD check in
> > clock_get_array_id(). Everything else just falls in place.
>
> For me, I am not sure if either version is really very pretty or easy
> to understand.
Well, if we document clock_get_array_id() proper, then it's easier to
follow than 10 dispatch functions which have all the same checks and
comparisons inside.
> My instinct is to keep the posix_cpu_X and pc_X functions out of the
> array of static clock id functions, if only to make the distinction
> between the legacy static ids and new dynamic ids clear.
>
> The conclusion from the "dynamic clock as file" discussion was that we
> don't want to add any more static clock ids, and new clocks should use
> the new, dynamic clock API. So, we should discourage any future
> attempt to add to that function array!
These IDs are not public, they are helpers to make the code simpler,
nothing else. I agree, that we don't want to extend the static ids for
public consumption, but implementation wise we can do that confined to
posix-timers.c.
> Having said that, if you insist on it, I won't mind reworking the
> dispatch code as you suggested.
I'm not insisting. I just saw the repeated if/else constructs and
added the clockfd check mentally :) That's where I started to wonder
about a way to do the same thing with way less lines of code.
> > > clock_nanosleep_restart(struct restart_block *restart_block)
> > > {
> > > - clockid_t which_clock = restart_block->arg0;
> > > -
> >
> > How does that compile ?
>
> Because the CLOCK_DISPATCH macro, above, makes no use of the first
Missed that, sorry.
> argument! I have removed the macro for the next round.
Cool.
Thanks,
tglx
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2011-01-12 11:16 ` Thomas Gleixner
(?)
@ 2011-01-12 12:17 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-12 12:17 UTC (permalink / raw)
To: Thomas Gleixner
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti
On Wed, Jan 12, 2011 at 12:16:10PM +0100, Thomas Gleixner wrote:
>
> We probably can remove the dispatch inlines that way completely and do
> the call directly from the syscall function.
Right, okay. I also wanted to put the whole logic into the syscall
itself. I'll try it that way.
Thanks,
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2011-01-11 12:57 ` Thomas Gleixner
@ 2011-01-13 4:30 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-13 4:30 UTC (permalink / raw)
To: Thomas Gleixner
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti
On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
>
> static clockid_t clock_get_array_id(const clockid_t id)
> {
> if (id >= 0)
> return id < MAX_CLOCKS ? id : POSIX_INV_CLOCK_ID;
>
> if (clock_is_posix_cpu(id))
> return POSIX_CPU_CLOCK_ID;
>
> return POSIX_INV_CLOCK_ID;
> }
>
> static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
> {
> struct k_clock *clk = &posix_clocks[clock_get_array_id(id)];
>
> return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
> }
I would like to take this idea one step further, like so:
static struct k_clock *clockid_to_kclock(const clockid_t id)
{
if (id >= 0)
return id < MAX_CLOCKS ?
&posix_clocks[id] : &posix_clocks[POSIX_INV_CLOCK_ID];
...
}
SYSCALL( ... , const clockid_t id, struct timespec *ts)
{
struct k_clock *clk = clockid_to_kclock(id);
return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
}
What do you think?
Thanks,
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2011-01-13 4:30 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-13 4:30 UTC (permalink / raw)
To: Thomas Gleixner
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti
On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
>
> static clockid_t clock_get_array_id(const clockid_t id)
> {
> if (id >= 0)
> return id < MAX_CLOCKS ? id : POSIX_INV_CLOCK_ID;
>
> if (clock_is_posix_cpu(id))
> return POSIX_CPU_CLOCK_ID;
>
> return POSIX_INV_CLOCK_ID;
> }
>
> static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
> {
> struct k_clock *clk = &posix_clocks[clock_get_array_id(id)];
>
> return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
> }
I would like to take this idea one step further, like so:
static struct k_clock *clockid_to_kclock(const clockid_t id)
{
if (id >= 0)
return id < MAX_CLOCKS ?
&posix_clocks[id] : &posix_clocks[POSIX_INV_CLOCK_ID];
...
}
SYSCALL( ... , const clockid_t id, struct timespec *ts)
{
struct k_clock *clk = clockid_to_kclock(id);
return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
}
What do you think?
Thanks,
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread[parent not found: <20110113043037.GA17726-7KxsofuKt4IfAd9E5cN8NEzG7cXyKsk/@public.gmane.org>]
* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
2011-01-13 4:30 ` Richard Cochran
@ 2011-01-13 11:25 ` Thomas Gleixner
-1 siblings, 0 replies; 76+ messages in thread
From: Thomas Gleixner @ 2011-01-13 11:25 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti
On Thu, 13 Jan 2011, Richard Cochran wrote:
> On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
> >
> > static clockid_t clock_get_array_id(const clockid_t id)
> > {
> > if (id >= 0)
> > return id < MAX_CLOCKS ? id : POSIX_INV_CLOCK_ID;
> >
> > if (clock_is_posix_cpu(id))
> > return POSIX_CPU_CLOCK_ID;
> >
> > return POSIX_INV_CLOCK_ID;
> > }
> >
> > static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
> > {
> > struct k_clock *clk = &posix_clocks[clock_get_array_id(id)];
> >
> > return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
> > }
>
> I would like to take this idea one step further, like so:
>
> static struct k_clock *clockid_to_kclock(const clockid_t id)
> {
> if (id >= 0)
> return id < MAX_CLOCKS ?
> &posix_clocks[id] : &posix_clocks[POSIX_INV_CLOCK_ID];
> ...
> }
>
> SYSCALL( ... , const clockid_t id, struct timespec *ts)
> {
> struct k_clock *clk = clockid_to_kclock(id);
>
> return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
> }
>
> What do you think?
Yeah, that's even better!
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 08/13] posix clocks: cleanup the CLOCK_DISPTACH macro
@ 2011-01-13 11:25 ` Thomas Gleixner
0 siblings, 0 replies; 76+ messages in thread
From: Thomas Gleixner @ 2011-01-13 11:25 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Krzysztof Halasa,
Peter Zijlstra, Rodolfo Giometti
On Thu, 13 Jan 2011, Richard Cochran wrote:
> On Tue, Jan 11, 2011 at 01:57:23PM +0100, Thomas Gleixner wrote:
> >
> > static clockid_t clock_get_array_id(const clockid_t id)
> > {
> > if (id >= 0)
> > return id < MAX_CLOCKS ? id : POSIX_INV_CLOCK_ID;
> >
> > if (clock_is_posix_cpu(id))
> > return POSIX_CPU_CLOCK_ID;
> >
> > return POSIX_INV_CLOCK_ID;
> > }
> >
> > static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
> > {
> > struct k_clock *clk = &posix_clocks[clock_get_array_id(id)];
> >
> > return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
> > }
>
> I would like to take this idea one step further, like so:
>
> static struct k_clock *clockid_to_kclock(const clockid_t id)
> {
> if (id >= 0)
> return id < MAX_CLOCKS ?
> &posix_clocks[id] : &posix_clocks[POSIX_INV_CLOCK_ID];
> ...
> }
>
> SYSCALL( ... , const clockid_t id, struct timespec *ts)
> {
> struct k_clock *clk = clockid_to_kclock(id);
>
> return clk->clock_getres ? clk->clock_getres(id, ts) : -EINVAL;
> }
>
> What do you think?
Yeah, that's even better!
^ permalink raw reply [flat|nested] 76+ messages in thread
* [PATCH V8 09/13] posix clocks: introduce dynamic clocks
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:15 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:15 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
This patch adds support for adding and removing posix clocks. The
clock lifetime cycle is patterned after usb devices. Each clock is
represented by a standard character device. In addition, the driver
may optionally implemented custom character device operations.
The posix clock and timer system calls listed below now work with
dynamic posix clocks, as well as the traditional static clocks.
For the performance critical calls (eg clock_gettime) the code path
from the traditional static clocks is preserved.
The following system calls are affected:
- clock_adjtime (brand new syscall)
- clock_gettime
- clock_getres
- clock_settime
- timer_create
- timer_delete
- timer_gettime
- timer_settime
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
include/linux/posix-clock.h | 137 +++++++++++
include/linux/posix-timers.h | 5 +-
kernel/posix-timers.c | 19 +-
kernel/time/Makefile | 3 +-
kernel/time/posix-clock-syscalls.h | 39 +++
kernel/time/posix-clock.c | 454 ++++++++++++++++++++++++++++++++++++
6 files changed, 648 insertions(+), 9 deletions(-)
create mode 100644 include/linux/posix-clock.h
create mode 100644 kernel/time/posix-clock-syscalls.h
create mode 100644 kernel/time/posix-clock.c
diff --git a/include/linux/posix-clock.h b/include/linux/posix-clock.h
new file mode 100644
index 0000000..b8e3356
--- /dev/null
+++ b/include/linux/posix-clock.h
@@ -0,0 +1,137 @@
+/*
+ * posix-clock.h - support for dynamic clock devices
+ *
+ * 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 _LINUX_POSIX_CLOCK_H_
+#define _LINUX_POSIX_CLOCK_H_
+
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/posix-timers.h>
+
+/**
+ * struct posix_clock_operations - functional interface to the clock
+ *
+ * Every posix clock is represented by a character device. Drivers may
+ * optionally offer extended capabilities by implementing the
+ * character device methods. The character device file operations are
+ * first handled by the clock device layer, then passed on to the
+ * driver by calling these functions.
+ *
+ * Drivers should embed their posix_clock_operations within a private
+ * structure, obtaining a reference to it using container_of().
+ *
+ * @owner: The clock driver should set to THIS_MODULE
+ * @clock_adjtime: Adjust the clock
+ * @clock_gettime: Read the current time
+ * @clock_getres: Get the clock resolution
+ * @clock_settime: Set the current time value
+ * @timer_create: Create a new timer
+ * @timer_delete: Remove a previously created timer
+ * @timer_gettime: Get remaining time and interval of a timer
+ * @timer_setttime: Set a timer's initial expiration and interval
+ * @fasync: Optional character device fasync method
+ * @mmap: Optional character device mmap method
+ * @open: Optional character device open method
+ * @release: Optional character device release method
+ * @ioctl: Optional character device ioctl method
+ * @read: Optional character device read method
+ * @poll: Optional character device poll method
+ */
+struct posix_clock_operations {
+ struct module *owner;
+
+ int (*clock_adjtime)(struct posix_clock_operations *pc,
+ struct timex *tx);
+
+ int (*clock_gettime)(struct posix_clock_operations *pc,
+ struct timespec *ts);
+
+ int (*clock_getres) (struct posix_clock_operations *pc,
+ struct timespec *ts);
+
+ int (*clock_settime)(struct posix_clock_operations *pc,
+ const struct timespec *ts);
+
+ int (*timer_create) (struct posix_clock_operations *pc,
+ struct k_itimer *kit);
+
+ int (*timer_delete) (struct posix_clock_operations *pc,
+ struct k_itimer *kit);
+
+ void (*timer_gettime)(struct posix_clock_operations *pc,
+ struct k_itimer *kit, struct itimerspec *tsp);
+
+ int (*timer_settime)(struct posix_clock_operations *pc,
+ struct k_itimer *kit, int flags,
+ struct itimerspec *tsp, struct itimerspec *old);
+ /*
+ * Optional character device methods:
+ */
+ int (*fasync) (struct posix_clock_operations *pc,
+ int fd, struct file *file, int on);
+
+ long (*ioctl) (struct posix_clock_operations *pc,
+ unsigned int cmd, unsigned long arg);
+
+ int (*mmap) (struct posix_clock_operations *pc,
+ struct vm_area_struct *vma);
+
+ int (*open) (struct posix_clock_operations *pc, fmode_t f_mode);
+
+ uint (*poll) (struct posix_clock_operations *pc,
+ struct file *file, poll_table *wait);
+
+ int (*release) (struct posix_clock_operations *pc);
+
+ ssize_t (*read) (struct posix_clock_operations *pc,
+ uint flags, char __user *buf, size_t cnt);
+};
+
+/**
+ * struct posix_clock - an opaque type
+ */
+struct posix_clock;
+
+/**
+ * posix_clock_create() - register a new clock
+ * @cops: Pointer to the clock's interface
+ * @devid: Allocated device id
+ * @priv: Private data passed back to the driver via the interface functions
+ *
+ * A clock driver calls this function to register itself with the
+ * clock device subsystem. The 'cops' argument must point to
+ * persistent data, so the caller should pass a static global.
+ *
+ * Returns a pointer to a new clock device, or PTR_ERR on error.
+ */
+struct posix_clock *posix_clock_create(struct posix_clock_operations *pc,
+ dev_t devid);
+
+/**
+ * posix_clock_destroy() - unregister a clock
+ * @clk: Pointer obtained via posix_clock_create()
+ *
+ * A clock driver calls this function to remove itself from the clock
+ * device subsystem. The posix_clock itself will remain (in an
+ * inactive state) until its reference count drops to zero, at which
+ * point it will be deallocated.
+ */
+void posix_clock_destroy(struct posix_clock *clk);
+
+#endif
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index eef7f9c..1544482 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -33,7 +33,7 @@ struct cpu_timer_list {
#define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))
#define CPUCLOCK_PERTHREAD(clock) \
(((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0)
-#define CPUCLOCK_PID_MASK 7
+
#define CPUCLOCK_PERTHREAD_MASK 4
#define CPUCLOCK_WHICH(clock) ((clock) & (clockid_t) CPUCLOCK_CLOCK_MASK)
#define CPUCLOCK_CLOCK_MASK 3
@@ -49,6 +49,9 @@ struct cpu_timer_list {
#define MAKE_THREAD_CPUCLOCK(tid, clock) \
MAKE_PROCESS_CPUCLOCK((tid), (clock) | CPUCLOCK_PERTHREAD_MASK)
+#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)
+#define CLOCKID_TO_FD(clk) ((unsigned int) ~((clk) >> 3))
+
/* POSIX.1b interval timer structure. */
struct k_itimer {
struct list_head list; /* free/ allocate list */
diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c
index 65b0599..9a92aef 100644
--- a/kernel/posix-timers.c
+++ b/kernel/posix-timers.c
@@ -41,12 +41,15 @@
#include <linux/init.h>
#include <linux/compiler.h>
#include <linux/idr.h>
+#include <linux/posix-clock.h>
#include <linux/posix-timers.h>
#include <linux/syscalls.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/module.h>
+#include "time/posix-clock-syscalls.h"
+
/*
* Management arrays for POSIX timers. Timers are kept in slab memory
* Timer ids are allocated by an external routine that keeps track of the
@@ -545,7 +548,7 @@ static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_getres(id, ts);
- return -EINVAL;
+ return pc_clock_getres(id, ts);
}
static inline int dispatch_clock_set(const clockid_t id, struct timespec *ts)
@@ -558,7 +561,7 @@ static inline int dispatch_clock_set(const clockid_t id, struct timespec *ts)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_set(id, ts);
- return -EINVAL;
+ return pc_clock_settime(id, ts);
}
static inline int dispatch_clock_get(const clockid_t id, struct timespec *ts)
@@ -571,7 +574,7 @@ static inline int dispatch_clock_get(const clockid_t id, struct timespec *ts)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_get(id, ts);
- return -EINVAL;
+ return pc_clock_gettime(id, ts);
}
static inline int dispatch_clock_adj(const clockid_t id, struct timex *tx)
@@ -584,7 +587,7 @@ static inline int dispatch_clock_adj(const clockid_t id, struct timex *tx)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_adj(id, tx);
- return -EINVAL;
+ return pc_clock_adjtime(id, tx);
}
static inline int dispatch_timer_create(struct k_itimer *kit)
@@ -599,7 +602,7 @@ static inline int dispatch_timer_create(struct k_itimer *kit)
if (clock_is_posix_cpu(id))
return posix_cpu_timer_create(kit);
- return -EINVAL;
+ return pc_timer_create(kit);
}
static inline int dispatch_nsleep(const clockid_t id, int flags,
@@ -647,7 +650,7 @@ static inline int dispatch_timer_set(struct k_itimer *kit, int flags,
if (clock_is_posix_cpu(id))
return posix_cpu_timer_set(kit, flags, tsp, old);
- return -EINVAL;
+ return pc_timer_settime(kit, flags, tsp, old);
}
static inline int dispatch_timer_del(struct k_itimer *kit)
@@ -662,7 +665,7 @@ static inline int dispatch_timer_del(struct k_itimer *kit)
if (clock_is_posix_cpu(id))
return posix_cpu_timer_del(kit);
- return -EINVAL;
+ return pc_timer_delete(kit);
}
static inline void dispatch_timer_get(struct k_itimer *kit,
@@ -677,6 +680,8 @@ static inline void dispatch_timer_get(struct k_itimer *kit,
else if (clock_is_posix_cpu(id))
posix_cpu_timer_get(kit, tsp);
+ else
+ pc_timer_gettime(kit, tsp);
}
#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
diff --git a/kernel/time/Makefile b/kernel/time/Makefile
index ee26662..b042599 100644
--- a/kernel/time/Makefile
+++ b/kernel/time/Makefile
@@ -1,4 +1,5 @@
-obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o timeconv.o
+obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o
+obj-y += timeconv.o posix-clock.o
obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o
obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o
diff --git a/kernel/time/posix-clock-syscalls.h b/kernel/time/posix-clock-syscalls.h
new file mode 100644
index 0000000..6afa204
--- /dev/null
+++ b/kernel/time/posix-clock-syscalls.h
@@ -0,0 +1,39 @@
+/*
+ * posix-clock-syscalls.h - support for dynamic clock devices
+ *
+ * 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 _POSIX_CLOCK_SYSCALLS_H_
+#define _POSIX_CLOCK_SYSCALLS_H_
+
+#include <linux/posix-timers.h>
+
+/*
+ * The following functions are only to be called from posix-timers.c
+ */
+
+int pc_clock_adjtime(clockid_t id, struct timex *tx);
+int pc_clock_gettime(clockid_t id, struct timespec *ts);
+int pc_clock_getres(clockid_t id, struct timespec *ts);
+int pc_clock_settime(clockid_t id, const struct timespec *ts);
+int pc_timer_create(struct k_itimer *kt);
+int pc_timer_delete(struct k_itimer *kt);
+void pc_timer_gettime(struct k_itimer *kt, struct itimerspec *ts);
+int pc_timer_settime(struct k_itimer *kt, int flags,
+ struct itimerspec *ts, struct itimerspec *old);
+
+#endif
diff --git a/kernel/time/posix-clock.c b/kernel/time/posix-clock.c
new file mode 100644
index 0000000..a34f183
--- /dev/null
+++ b/kernel/time/posix-clock.c
@@ -0,0 +1,454 @@
+/*
+ * posix-clock.c - support for dynamic clock devices
+ *
+ * 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/cdev.h>
+#include <linux/device.h>
+#include <linux/file.h>
+#include <linux/mutex.h>
+#include <linux/posix-clock.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
+
+#include "posix-clock-syscalls.h"
+
+struct posix_clock {
+ struct posix_clock_operations *ops;
+ struct cdev cdev;
+ struct kref kref;
+ /* Protects the 'zombie' field from concurrent access. */
+ struct mutex mutex;
+ /* If 'zombie' is true, then the hardware has disappeared. */
+ bool zombie;
+};
+
+static void delete_clock(struct kref *kref);
+
+/*
+ * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
+ */
+static struct posix_clock *get_posix_clock(struct file *fp)
+{
+ struct posix_clock *clk = fp->private_data;
+
+ mutex_lock(&clk->mutex);
+
+ if (!clk->zombie)
+ return clk;
+
+ mutex_unlock(&clk->mutex);
+
+ return NULL;
+}
+
+static void put_posix_clock(struct posix_clock *clk)
+{
+ mutex_unlock(&clk->mutex);
+}
+
+static ssize_t posix_clock_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -EINVAL;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->read)
+ err = clk->ops->read(clk->ops, fp->f_flags, buf, count);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+static unsigned int posix_clock_poll(struct file *fp, poll_table *wait)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int result = 0;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->poll)
+ result = clk->ops->poll(clk->ops, fp, wait);
+
+ put_posix_clock(clk);
+
+ return result;
+}
+
+static int posix_clock_fasync(int fd, struct file *fp, int on)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = 0;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->fasync)
+ err = clk->ops->fasync(clk->ops, fd, fp, on);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+static int posix_clock_mmap(struct file *fp, struct vm_area_struct *vma)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -ENODEV;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->mmap)
+ err = clk->ops->mmap(clk->ops, vma);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+static long posix_clock_ioctl(struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -ENOTTY;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->ioctl)
+ err = clk->ops->ioctl(clk->ops, cmd, arg);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+#ifdef CONFIG_COMPAT
+static long posix_clock_compat_ioctl(struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -ENOTTY;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->ioctl)
+ err = clk->ops->ioctl(clk->ops, cmd, arg);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+#endif
+
+static int posix_clock_open(struct inode *inode, struct file *fp)
+{
+ int err;
+ struct posix_clock *clk =
+ container_of(inode->i_cdev, struct posix_clock, cdev);
+
+ mutex_lock(&clk->mutex);
+
+ if (clk->zombie) {
+ err = -ENODEV;
+ goto out;
+ }
+ if (clk->ops->open)
+ err = clk->ops->open(clk->ops, fp->f_mode);
+ else
+ err = 0;
+
+ if (!err) {
+ kref_get(&clk->kref);
+ fp->private_data = clk;
+ }
+out:
+ mutex_unlock(&clk->mutex);
+ return err;
+}
+
+static int posix_clock_release(struct inode *inode, struct file *fp)
+{
+ struct posix_clock *clk = fp->private_data;
+ int err = 0;
+
+ if (clk->ops->release)
+ err = clk->ops->release(clk->ops);
+
+ kref_put(&clk->kref, delete_clock);
+
+ fp->private_data = NULL;
+
+ return err;
+}
+
+static const struct file_operations posix_clock_file_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = posix_clock_read,
+ .poll = posix_clock_poll,
+ .unlocked_ioctl = posix_clock_ioctl,
+ .open = posix_clock_open,
+ .release = posix_clock_release,
+ .fasync = posix_clock_fasync,
+ .mmap = posix_clock_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = posix_clock_compat_ioctl,
+#endif
+};
+
+struct posix_clock *posix_clock_create(struct posix_clock_operations *pc,
+ dev_t devid)
+{
+ struct posix_clock *clk;
+ int err;
+
+ err = -ENOMEM;
+ clk = kzalloc(sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ goto no_memory;
+
+ clk->ops = pc;
+ kref_init(&clk->kref);
+ mutex_init(&clk->mutex);
+
+ cdev_init(&clk->cdev, &posix_clock_file_operations);
+ clk->cdev.owner = clk->ops->owner;
+ err = cdev_add(&clk->cdev, devid, 1);
+ if (err)
+ goto no_cdev;
+
+ return clk;
+
+no_cdev:
+ mutex_destroy(&clk->mutex);
+ kfree(clk);
+no_memory:
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(posix_clock_create);
+
+static void delete_clock(struct kref *kref)
+{
+ struct posix_clock *clk = container_of(kref, struct posix_clock, kref);
+ mutex_destroy(&clk->mutex);
+ kfree(clk);
+}
+
+void posix_clock_destroy(struct posix_clock *clk)
+{
+ cdev_del(&clk->cdev);
+
+ mutex_lock(&clk->mutex);
+ clk->zombie = true;
+ mutex_unlock(&clk->mutex);
+
+ kref_put(&clk->kref, delete_clock);
+}
+EXPORT_SYMBOL_GPL(posix_clock_destroy);
+
+struct posix_clock_desc {
+ struct file *fp;
+ struct posix_clock *clk;
+};
+
+static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd)
+{
+ struct file *fp = fget(CLOCKID_TO_FD(id));
+ int err = -EINVAL;
+
+ if (!fp)
+ return err;
+
+ if (fp->f_op->open != posix_clock_open || !fp->private_data)
+ goto out;
+
+ cd->fp = fp;
+ cd->clk = get_posix_clock(fp);
+
+ err = cd->clk ? 0 : -ENODEV;
+out:
+ if (err)
+ fput(fp);
+ return err;
+}
+
+static void put_clock_desc(struct posix_clock_desc *cd)
+{
+ put_posix_clock(cd->clk);
+ fput(cd->fp);
+}
+
+int pc_clock_adjtime(clockid_t id, struct timex *tx)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_adjtime)
+ err = cd.clk->ops->clock_adjtime(cd.clk->ops, tx);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_clock_gettime(clockid_t id, struct timespec *ts)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_gettime)
+ err = cd.clk->ops->clock_gettime(cd.clk->ops, ts);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_clock_getres(clockid_t id, struct timespec *ts)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_getres)
+ err = cd.clk->ops->clock_getres(cd.clk->ops, ts);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_clock_settime(clockid_t id, const struct timespec *ts)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_settime)
+ err = cd.clk->ops->clock_settime(cd.clk->ops, ts);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_timer_create(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->timer_create)
+ err = cd.clk->ops->timer_create(cd.clk->ops, kit);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_timer_delete(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->timer_delete)
+ err = cd.clk->ops->timer_delete(cd.clk->ops, kit);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+void pc_timer_gettime(struct k_itimer *kit, struct itimerspec *ts)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+
+ if (get_clock_desc(id, &cd))
+ return;
+
+ if (cd.clk->ops->timer_gettime)
+ cd.clk->ops->timer_gettime(cd.clk->ops, kit, ts);
+
+ put_clock_desc(&cd);
+}
+
+int pc_timer_settime(struct k_itimer *kit, int flags,
+ struct itimerspec *ts, struct itimerspec *old)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->timer_settime)
+ err = cd.clk->ops->timer_settime(cd.clk->ops,
+ kit, flags, ts, old);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 09/13] posix clocks: introduce dynamic clocks
@ 2010-12-31 19:15 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:15 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
This patch adds support for adding and removing posix clocks. The
clock lifetime cycle is patterned after usb devices. Each clock is
represented by a standard character device. In addition, the driver
may optionally implemented custom character device operations.
The posix clock and timer system calls listed below now work with
dynamic posix clocks, as well as the traditional static clocks.
For the performance critical calls (eg clock_gettime) the code path
from the traditional static clocks is preserved.
The following system calls are affected:
- clock_adjtime (brand new syscall)
- clock_gettime
- clock_getres
- clock_settime
- timer_create
- timer_delete
- timer_gettime
- timer_settime
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
include/linux/posix-clock.h | 137 +++++++++++
include/linux/posix-timers.h | 5 +-
kernel/posix-timers.c | 19 +-
kernel/time/Makefile | 3 +-
kernel/time/posix-clock-syscalls.h | 39 +++
kernel/time/posix-clock.c | 454 ++++++++++++++++++++++++++++++++++++
6 files changed, 648 insertions(+), 9 deletions(-)
create mode 100644 include/linux/posix-clock.h
create mode 100644 kernel/time/posix-clock-syscalls.h
create mode 100644 kernel/time/posix-clock.c
diff --git a/include/linux/posix-clock.h b/include/linux/posix-clock.h
new file mode 100644
index 0000000..b8e3356
--- /dev/null
+++ b/include/linux/posix-clock.h
@@ -0,0 +1,137 @@
+/*
+ * posix-clock.h - support for dynamic clock devices
+ *
+ * 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 _LINUX_POSIX_CLOCK_H_
+#define _LINUX_POSIX_CLOCK_H_
+
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/posix-timers.h>
+
+/**
+ * struct posix_clock_operations - functional interface to the clock
+ *
+ * Every posix clock is represented by a character device. Drivers may
+ * optionally offer extended capabilities by implementing the
+ * character device methods. The character device file operations are
+ * first handled by the clock device layer, then passed on to the
+ * driver by calling these functions.
+ *
+ * Drivers should embed their posix_clock_operations within a private
+ * structure, obtaining a reference to it using container_of().
+ *
+ * @owner: The clock driver should set to THIS_MODULE
+ * @clock_adjtime: Adjust the clock
+ * @clock_gettime: Read the current time
+ * @clock_getres: Get the clock resolution
+ * @clock_settime: Set the current time value
+ * @timer_create: Create a new timer
+ * @timer_delete: Remove a previously created timer
+ * @timer_gettime: Get remaining time and interval of a timer
+ * @timer_setttime: Set a timer's initial expiration and interval
+ * @fasync: Optional character device fasync method
+ * @mmap: Optional character device mmap method
+ * @open: Optional character device open method
+ * @release: Optional character device release method
+ * @ioctl: Optional character device ioctl method
+ * @read: Optional character device read method
+ * @poll: Optional character device poll method
+ */
+struct posix_clock_operations {
+ struct module *owner;
+
+ int (*clock_adjtime)(struct posix_clock_operations *pc,
+ struct timex *tx);
+
+ int (*clock_gettime)(struct posix_clock_operations *pc,
+ struct timespec *ts);
+
+ int (*clock_getres) (struct posix_clock_operations *pc,
+ struct timespec *ts);
+
+ int (*clock_settime)(struct posix_clock_operations *pc,
+ const struct timespec *ts);
+
+ int (*timer_create) (struct posix_clock_operations *pc,
+ struct k_itimer *kit);
+
+ int (*timer_delete) (struct posix_clock_operations *pc,
+ struct k_itimer *kit);
+
+ void (*timer_gettime)(struct posix_clock_operations *pc,
+ struct k_itimer *kit, struct itimerspec *tsp);
+
+ int (*timer_settime)(struct posix_clock_operations *pc,
+ struct k_itimer *kit, int flags,
+ struct itimerspec *tsp, struct itimerspec *old);
+ /*
+ * Optional character device methods:
+ */
+ int (*fasync) (struct posix_clock_operations *pc,
+ int fd, struct file *file, int on);
+
+ long (*ioctl) (struct posix_clock_operations *pc,
+ unsigned int cmd, unsigned long arg);
+
+ int (*mmap) (struct posix_clock_operations *pc,
+ struct vm_area_struct *vma);
+
+ int (*open) (struct posix_clock_operations *pc, fmode_t f_mode);
+
+ uint (*poll) (struct posix_clock_operations *pc,
+ struct file *file, poll_table *wait);
+
+ int (*release) (struct posix_clock_operations *pc);
+
+ ssize_t (*read) (struct posix_clock_operations *pc,
+ uint flags, char __user *buf, size_t cnt);
+};
+
+/**
+ * struct posix_clock - an opaque type
+ */
+struct posix_clock;
+
+/**
+ * posix_clock_create() - register a new clock
+ * @cops: Pointer to the clock's interface
+ * @devid: Allocated device id
+ * @priv: Private data passed back to the driver via the interface functions
+ *
+ * A clock driver calls this function to register itself with the
+ * clock device subsystem. The 'cops' argument must point to
+ * persistent data, so the caller should pass a static global.
+ *
+ * Returns a pointer to a new clock device, or PTR_ERR on error.
+ */
+struct posix_clock *posix_clock_create(struct posix_clock_operations *pc,
+ dev_t devid);
+
+/**
+ * posix_clock_destroy() - unregister a clock
+ * @clk: Pointer obtained via posix_clock_create()
+ *
+ * A clock driver calls this function to remove itself from the clock
+ * device subsystem. The posix_clock itself will remain (in an
+ * inactive state) until its reference count drops to zero, at which
+ * point it will be deallocated.
+ */
+void posix_clock_destroy(struct posix_clock *clk);
+
+#endif
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index eef7f9c..1544482 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -33,7 +33,7 @@ struct cpu_timer_list {
#define CPUCLOCK_PID(clock) ((pid_t) ~((clock) >> 3))
#define CPUCLOCK_PERTHREAD(clock) \
(((clock) & (clockid_t) CPUCLOCK_PERTHREAD_MASK) != 0)
-#define CPUCLOCK_PID_MASK 7
+
#define CPUCLOCK_PERTHREAD_MASK 4
#define CPUCLOCK_WHICH(clock) ((clock) & (clockid_t) CPUCLOCK_CLOCK_MASK)
#define CPUCLOCK_CLOCK_MASK 3
@@ -49,6 +49,9 @@ struct cpu_timer_list {
#define MAKE_THREAD_CPUCLOCK(tid, clock) \
MAKE_PROCESS_CPUCLOCK((tid), (clock) | CPUCLOCK_PERTHREAD_MASK)
+#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)
+#define CLOCKID_TO_FD(clk) ((unsigned int) ~((clk) >> 3))
+
/* POSIX.1b interval timer structure. */
struct k_itimer {
struct list_head list; /* free/ allocate list */
diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c
index 65b0599..9a92aef 100644
--- a/kernel/posix-timers.c
+++ b/kernel/posix-timers.c
@@ -41,12 +41,15 @@
#include <linux/init.h>
#include <linux/compiler.h>
#include <linux/idr.h>
+#include <linux/posix-clock.h>
#include <linux/posix-timers.h>
#include <linux/syscalls.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/module.h>
+#include "time/posix-clock-syscalls.h"
+
/*
* Management arrays for POSIX timers. Timers are kept in slab memory
* Timer ids are allocated by an external routine that keeps track of the
@@ -545,7 +548,7 @@ static inline int dispatch_clock_getres(const clockid_t id, struct timespec *ts)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_getres(id, ts);
- return -EINVAL;
+ return pc_clock_getres(id, ts);
}
static inline int dispatch_clock_set(const clockid_t id, struct timespec *ts)
@@ -558,7 +561,7 @@ static inline int dispatch_clock_set(const clockid_t id, struct timespec *ts)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_set(id, ts);
- return -EINVAL;
+ return pc_clock_settime(id, ts);
}
static inline int dispatch_clock_get(const clockid_t id, struct timespec *ts)
@@ -571,7 +574,7 @@ static inline int dispatch_clock_get(const clockid_t id, struct timespec *ts)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_get(id, ts);
- return -EINVAL;
+ return pc_clock_gettime(id, ts);
}
static inline int dispatch_clock_adj(const clockid_t id, struct timex *tx)
@@ -584,7 +587,7 @@ static inline int dispatch_clock_adj(const clockid_t id, struct timex *tx)
if (clock_is_posix_cpu(id))
return posix_cpu_clock_adj(id, tx);
- return -EINVAL;
+ return pc_clock_adjtime(id, tx);
}
static inline int dispatch_timer_create(struct k_itimer *kit)
@@ -599,7 +602,7 @@ static inline int dispatch_timer_create(struct k_itimer *kit)
if (clock_is_posix_cpu(id))
return posix_cpu_timer_create(kit);
- return -EINVAL;
+ return pc_timer_create(kit);
}
static inline int dispatch_nsleep(const clockid_t id, int flags,
@@ -647,7 +650,7 @@ static inline int dispatch_timer_set(struct k_itimer *kit, int flags,
if (clock_is_posix_cpu(id))
return posix_cpu_timer_set(kit, flags, tsp, old);
- return -EINVAL;
+ return pc_timer_settime(kit, flags, tsp, old);
}
static inline int dispatch_timer_del(struct k_itimer *kit)
@@ -662,7 +665,7 @@ static inline int dispatch_timer_del(struct k_itimer *kit)
if (clock_is_posix_cpu(id))
return posix_cpu_timer_del(kit);
- return -EINVAL;
+ return pc_timer_delete(kit);
}
static inline void dispatch_timer_get(struct k_itimer *kit,
@@ -677,6 +680,8 @@ static inline void dispatch_timer_get(struct k_itimer *kit,
else if (clock_is_posix_cpu(id))
posix_cpu_timer_get(kit, tsp);
+ else
+ pc_timer_gettime(kit, tsp);
}
#define CLOCK_DISPATCH(clock, call, arglist) dispatch_##call arglist
diff --git a/kernel/time/Makefile b/kernel/time/Makefile
index ee26662..b042599 100644
--- a/kernel/time/Makefile
+++ b/kernel/time/Makefile
@@ -1,4 +1,5 @@
-obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o timeconv.o
+obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o
+obj-y += timeconv.o posix-clock.o
obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o
obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o
diff --git a/kernel/time/posix-clock-syscalls.h b/kernel/time/posix-clock-syscalls.h
new file mode 100644
index 0000000..6afa204
--- /dev/null
+++ b/kernel/time/posix-clock-syscalls.h
@@ -0,0 +1,39 @@
+/*
+ * posix-clock-syscalls.h - support for dynamic clock devices
+ *
+ * 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 _POSIX_CLOCK_SYSCALLS_H_
+#define _POSIX_CLOCK_SYSCALLS_H_
+
+#include <linux/posix-timers.h>
+
+/*
+ * The following functions are only to be called from posix-timers.c
+ */
+
+int pc_clock_adjtime(clockid_t id, struct timex *tx);
+int pc_clock_gettime(clockid_t id, struct timespec *ts);
+int pc_clock_getres(clockid_t id, struct timespec *ts);
+int pc_clock_settime(clockid_t id, const struct timespec *ts);
+int pc_timer_create(struct k_itimer *kt);
+int pc_timer_delete(struct k_itimer *kt);
+void pc_timer_gettime(struct k_itimer *kt, struct itimerspec *ts);
+int pc_timer_settime(struct k_itimer *kt, int flags,
+ struct itimerspec *ts, struct itimerspec *old);
+
+#endif
diff --git a/kernel/time/posix-clock.c b/kernel/time/posix-clock.c
new file mode 100644
index 0000000..a34f183
--- /dev/null
+++ b/kernel/time/posix-clock.c
@@ -0,0 +1,454 @@
+/*
+ * posix-clock.c - support for dynamic clock devices
+ *
+ * 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/cdev.h>
+#include <linux/device.h>
+#include <linux/file.h>
+#include <linux/mutex.h>
+#include <linux/posix-clock.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
+
+#include "posix-clock-syscalls.h"
+
+struct posix_clock {
+ struct posix_clock_operations *ops;
+ struct cdev cdev;
+ struct kref kref;
+ /* Protects the 'zombie' field from concurrent access. */
+ struct mutex mutex;
+ /* If 'zombie' is true, then the hardware has disappeared. */
+ bool zombie;
+};
+
+static void delete_clock(struct kref *kref);
+
+/*
+ * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
+ */
+static struct posix_clock *get_posix_clock(struct file *fp)
+{
+ struct posix_clock *clk = fp->private_data;
+
+ mutex_lock(&clk->mutex);
+
+ if (!clk->zombie)
+ return clk;
+
+ mutex_unlock(&clk->mutex);
+
+ return NULL;
+}
+
+static void put_posix_clock(struct posix_clock *clk)
+{
+ mutex_unlock(&clk->mutex);
+}
+
+static ssize_t posix_clock_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -EINVAL;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->read)
+ err = clk->ops->read(clk->ops, fp->f_flags, buf, count);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+static unsigned int posix_clock_poll(struct file *fp, poll_table *wait)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int result = 0;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->poll)
+ result = clk->ops->poll(clk->ops, fp, wait);
+
+ put_posix_clock(clk);
+
+ return result;
+}
+
+static int posix_clock_fasync(int fd, struct file *fp, int on)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = 0;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->fasync)
+ err = clk->ops->fasync(clk->ops, fd, fp, on);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+static int posix_clock_mmap(struct file *fp, struct vm_area_struct *vma)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -ENODEV;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->mmap)
+ err = clk->ops->mmap(clk->ops, vma);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+static long posix_clock_ioctl(struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -ENOTTY;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->ioctl)
+ err = clk->ops->ioctl(clk->ops, cmd, arg);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+
+#ifdef CONFIG_COMPAT
+static long posix_clock_compat_ioctl(struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct posix_clock *clk = get_posix_clock(fp);
+ int err = -ENOTTY;
+
+ if (!clk)
+ return -ENODEV;
+
+ if (clk->ops->ioctl)
+ err = clk->ops->ioctl(clk->ops, cmd, arg);
+
+ put_posix_clock(clk);
+
+ return err;
+}
+#endif
+
+static int posix_clock_open(struct inode *inode, struct file *fp)
+{
+ int err;
+ struct posix_clock *clk =
+ container_of(inode->i_cdev, struct posix_clock, cdev);
+
+ mutex_lock(&clk->mutex);
+
+ if (clk->zombie) {
+ err = -ENODEV;
+ goto out;
+ }
+ if (clk->ops->open)
+ err = clk->ops->open(clk->ops, fp->f_mode);
+ else
+ err = 0;
+
+ if (!err) {
+ kref_get(&clk->kref);
+ fp->private_data = clk;
+ }
+out:
+ mutex_unlock(&clk->mutex);
+ return err;
+}
+
+static int posix_clock_release(struct inode *inode, struct file *fp)
+{
+ struct posix_clock *clk = fp->private_data;
+ int err = 0;
+
+ if (clk->ops->release)
+ err = clk->ops->release(clk->ops);
+
+ kref_put(&clk->kref, delete_clock);
+
+ fp->private_data = NULL;
+
+ return err;
+}
+
+static const struct file_operations posix_clock_file_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = posix_clock_read,
+ .poll = posix_clock_poll,
+ .unlocked_ioctl = posix_clock_ioctl,
+ .open = posix_clock_open,
+ .release = posix_clock_release,
+ .fasync = posix_clock_fasync,
+ .mmap = posix_clock_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = posix_clock_compat_ioctl,
+#endif
+};
+
+struct posix_clock *posix_clock_create(struct posix_clock_operations *pc,
+ dev_t devid)
+{
+ struct posix_clock *clk;
+ int err;
+
+ err = -ENOMEM;
+ clk = kzalloc(sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ goto no_memory;
+
+ clk->ops = pc;
+ kref_init(&clk->kref);
+ mutex_init(&clk->mutex);
+
+ cdev_init(&clk->cdev, &posix_clock_file_operations);
+ clk->cdev.owner = clk->ops->owner;
+ err = cdev_add(&clk->cdev, devid, 1);
+ if (err)
+ goto no_cdev;
+
+ return clk;
+
+no_cdev:
+ mutex_destroy(&clk->mutex);
+ kfree(clk);
+no_memory:
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(posix_clock_create);
+
+static void delete_clock(struct kref *kref)
+{
+ struct posix_clock *clk = container_of(kref, struct posix_clock, kref);
+ mutex_destroy(&clk->mutex);
+ kfree(clk);
+}
+
+void posix_clock_destroy(struct posix_clock *clk)
+{
+ cdev_del(&clk->cdev);
+
+ mutex_lock(&clk->mutex);
+ clk->zombie = true;
+ mutex_unlock(&clk->mutex);
+
+ kref_put(&clk->kref, delete_clock);
+}
+EXPORT_SYMBOL_GPL(posix_clock_destroy);
+
+struct posix_clock_desc {
+ struct file *fp;
+ struct posix_clock *clk;
+};
+
+static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd)
+{
+ struct file *fp = fget(CLOCKID_TO_FD(id));
+ int err = -EINVAL;
+
+ if (!fp)
+ return err;
+
+ if (fp->f_op->open != posix_clock_open || !fp->private_data)
+ goto out;
+
+ cd->fp = fp;
+ cd->clk = get_posix_clock(fp);
+
+ err = cd->clk ? 0 : -ENODEV;
+out:
+ if (err)
+ fput(fp);
+ return err;
+}
+
+static void put_clock_desc(struct posix_clock_desc *cd)
+{
+ put_posix_clock(cd->clk);
+ fput(cd->fp);
+}
+
+int pc_clock_adjtime(clockid_t id, struct timex *tx)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_adjtime)
+ err = cd.clk->ops->clock_adjtime(cd.clk->ops, tx);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_clock_gettime(clockid_t id, struct timespec *ts)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_gettime)
+ err = cd.clk->ops->clock_gettime(cd.clk->ops, ts);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_clock_getres(clockid_t id, struct timespec *ts)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_getres)
+ err = cd.clk->ops->clock_getres(cd.clk->ops, ts);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_clock_settime(clockid_t id, const struct timespec *ts)
+{
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->clock_settime)
+ err = cd.clk->ops->clock_settime(cd.clk->ops, ts);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_timer_create(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->timer_create)
+ err = cd.clk->ops->timer_create(cd.clk->ops, kit);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+int pc_timer_delete(struct k_itimer *kit)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->timer_delete)
+ err = cd.clk->ops->timer_delete(cd.clk->ops, kit);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
+
+void pc_timer_gettime(struct k_itimer *kit, struct itimerspec *ts)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+
+ if (get_clock_desc(id, &cd))
+ return;
+
+ if (cd.clk->ops->timer_gettime)
+ cd.clk->ops->timer_gettime(cd.clk->ops, kit, ts);
+
+ put_clock_desc(&cd);
+}
+
+int pc_timer_settime(struct k_itimer *kit, int flags,
+ struct itimerspec *ts, struct itimerspec *old)
+{
+ clockid_t id = kit->it_clock;
+ struct posix_clock_desc cd;
+ int err;
+
+ err = get_clock_desc(id, &cd);
+ if (err)
+ return err;
+
+ if (cd.clk->ops->timer_settime)
+ err = cd.clk->ops->timer_settime(cd.clk->ops,
+ kit, flags, ts, old);
+ else
+ err = -EOPNOTSUPP;
+
+ put_clock_desc(&cd);
+
+ return err;
+}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread[parent not found: <c28004fc5d121c1dd5f842550663fc9d6f55a114.1293820862.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>]
* Re: [PATCH V8 09/13] posix clocks: introduce dynamic clocks
2010-12-31 19:15 ` Richard Cochran
@ 2011-01-06 19:56 ` Arnd Bergmann
-1 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2011-01-06 19:56 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Christoph Lameter, David Miller, John Stultz,
Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
On Friday 31 December 2010, Richard Cochran wrote:
> This patch adds support for adding and removing posix clocks. The
> clock lifetime cycle is patterned after usb devices. Each clock is
> represented by a standard character device. In addition, the driver
> may optionally implemented custom character device operations.
>
> The posix clock and timer system calls listed below now work with
> dynamic posix clocks, as well as the traditional static clocks.
> For the performance critical calls (eg clock_gettime) the code path
> from the traditional static clocks is preserved.
Combining the operations structures and using container_of as you did
looks much better than before, but I had something slightly different
in mind:
The way that other subsystems do this is to pass a pointer to the
actual low-level object (struct posix_clock in your case) to the
abstracted functions, while you pass a pointer to the operations
structure. This has the advantage of keeping the definition of
posix_clock private to posix-clock.c, but it is something we do
very rarely.
I can see disadvantages with your approach: You still need to dynamically
allocate the posix_clock in posix_clock_create(), and the operations
structure cannot be const, which is a theoretical security problem
if there is a hole that can replace one of the pointers with a
reference to user memory.
I would recommend changing this to the more common model, where you
passs the (publically defined) struct posix_clock to all operations
and can do something like:
struct my_posix_clock {
...
struct posix_clock pclk;
} this_clock = {
...
.pclk = {
.ops = &my_posix_clock_operations,
}
};
int my_init(void)
{
return posix_clock_operations_register(&my_posix_clock.pclk);
}
void my_exit(void)
{
posix_clock_operations_unregister(&my_posix_clock.pclk);
}
It should be just a trivial change and just affect how easy it is for
other people to understand the code if they are already familiar
with other kernel code.
Overall, your series looks really good now, it would be nice if this
could still make it into 2.6.38.
Arnd
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 09/13] posix clocks: introduce dynamic clocks
@ 2011-01-06 19:56 ` Arnd Bergmann
0 siblings, 0 replies; 76+ messages in thread
From: Arnd Bergmann @ 2011-01-06 19:56 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
On Friday 31 December 2010, Richard Cochran wrote:
> This patch adds support for adding and removing posix clocks. The
> clock lifetime cycle is patterned after usb devices. Each clock is
> represented by a standard character device. In addition, the driver
> may optionally implemented custom character device operations.
>
> The posix clock and timer system calls listed below now work with
> dynamic posix clocks, as well as the traditional static clocks.
> For the performance critical calls (eg clock_gettime) the code path
> from the traditional static clocks is preserved.
Combining the operations structures and using container_of as you did
looks much better than before, but I had something slightly different
in mind:
The way that other subsystems do this is to pass a pointer to the
actual low-level object (struct posix_clock in your case) to the
abstracted functions, while you pass a pointer to the operations
structure. This has the advantage of keeping the definition of
posix_clock private to posix-clock.c, but it is something we do
very rarely.
I can see disadvantages with your approach: You still need to dynamically
allocate the posix_clock in posix_clock_create(), and the operations
structure cannot be const, which is a theoretical security problem
if there is a hole that can replace one of the pointers with a
reference to user memory.
I would recommend changing this to the more common model, where you
passs the (publically defined) struct posix_clock to all operations
and can do something like:
struct my_posix_clock {
...
struct posix_clock pclk;
} this_clock = {
...
.pclk = {
.ops = &my_posix_clock_operations,
}
};
int my_init(void)
{
return posix_clock_operations_register(&my_posix_clock.pclk);
}
void my_exit(void)
{
posix_clock_operations_unregister(&my_posix_clock.pclk);
}
It should be just a trivial change and just affect how easy it is for
other people to understand the code if they are already familiar
with other kernel code.
Overall, your series looks really good now, it would be nice if this
could still make it into 2.6.38.
Arnd
^ permalink raw reply [flat|nested] 76+ messages in thread* Re: [PATCH V8 09/13] posix clocks: introduce dynamic clocks
2011-01-06 19:56 ` Arnd Bergmann
(?)
@ 2011-01-07 16:41 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2011-01-07 16:41 UTC (permalink / raw)
To: Arnd Bergmann
Cc: linux-kernel, linux-api, netdev, Alan Cox, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
On Thu, Jan 06, 2011 at 08:56:22PM +0100, Arnd Bergmann wrote:
> It should be just a trivial change and just affect how easy it is for
> other people to understand the code if they are already familiar
> with other kernel code.
Okay, I'll get right on it.
> Overall, your series looks really good now, it would be nice if this
> could still make it into 2.6.38.
Yes, that would be great.
Thanks,
Richard
^ permalink raw reply [flat|nested] 76+ messages in thread
* [PATCH V8 11/13] ptp: Added a clock that uses the eTSEC found on the MPC85xx.
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:16 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:16 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
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, one alarm, and the PPS callback.
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
Documentation/powerpc/dts-bindings/fsl/tsec.txt | 57 +++
arch/powerpc/boot/dts/mpc8313erdb.dts | 14 +
arch/powerpc/boot/dts/mpc8572ds.dts | 14 +
arch/powerpc/boot/dts/p2020ds.dts | 14 +
arch/powerpc/boot/dts/p2020rdb.dts | 14 +
drivers/net/Makefile | 1 +
drivers/net/gianfar_ptp.c | 448 +++++++++++++++++++++++
drivers/net/gianfar_ptp_reg.h | 113 ++++++
drivers/ptp/Kconfig | 13 +
9 files changed, 688 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..f6edbb8 100644
--- a/Documentation/powerpc/dts-bindings/fsl/tsec.txt
+++ b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
@@ -74,3 +74,60 @@ 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.
+ - cksel 0= external clock, 1= eTSEC system clock, 3= RTC clock input.
+ Currently the driver only supports choice "1".
+ - tmr-fiper1 Fixed interval period pulse generator.
+ - tmr-fiper2 Fixed interval period pulse generator.
+ - max-adj Maximum frequency adjustment in parts per billion.
+
+ 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
+ max_adj = 1000000000 * (FreqDivRatio - 1.0) - 1
+
+ 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>;
+ max-adj = <659999998>;
+ };
diff --git a/arch/powerpc/boot/dts/mpc8313erdb.dts b/arch/powerpc/boot/dts/mpc8313erdb.dts
index 183f2aa..85a7eaa 100644
--- a/arch/powerpc/boot/dts/mpc8313erdb.dts
+++ b/arch/powerpc/boot/dts/mpc8313erdb.dts
@@ -208,6 +208,20 @@
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>;
+ max-adj = <659999998>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/mpc8572ds.dts b/arch/powerpc/boot/dts/mpc8572ds.dts
index cafc128..74208cd 100644
--- a/arch/powerpc/boot/dts/mpc8572ds.dts
+++ b/arch/powerpc/boot/dts/mpc8572ds.dts
@@ -324,6 +324,20 @@
};
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2 71 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xAAAAAAAB>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x3B9AC9FB>;
+ max-adj = <499999999>;
+ };
+
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..39d73bb 100644
--- a/arch/powerpc/boot/dts/p2020ds.dts
+++ b/arch/powerpc/boot/dts/p2020ds.dts
@@ -336,6 +336,20 @@
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>;
+ max-adj = <249999999>;
+ };
+
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..5498fb9 100644
--- a/arch/powerpc/boot/dts/p2020rdb.dts
+++ b/arch/powerpc/boot/dts/p2020rdb.dts
@@ -396,6 +396,20 @@
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>;
+ max-adj = <249999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..c303f5f 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -31,6 +31,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..84fff15
--- /dev/null
+++ b/drivers/net/gianfar_ptp.c
@@ -0,0 +1,448 @@
+/*
+ * 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;
+ struct ptp_clock_info caps;
+ 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;
+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 (val & PP1) {
+ ack |= PP1;
+ event.type = PTP_CLOCK_PPS;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (ack) {
+ gfar_write(&etsects->regs->tmr_tevent, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_gianfar_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ u64 adj;
+ u32 diff, tmr_add;
+ int neg_adj = 0;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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(struct ptp_clock_info *ptp, s64 delta)
+{
+ s64 now;
+ unsigned long flags;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+ unsigned long flags;
+ u32 bit, mask;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ switch (rq->extts.index) {
+ case 0:
+ bit = ETS1EN;
+ break;
+ case 1:
+ bit = ETS2EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= bit;
+ else
+ mask &= ~bit;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ case PTP_CLK_REQ_PPS:
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= PP1EN;
+ else
+ mask &= ~PP1EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ default:
+ break;
+ }
+
+ 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 = 1,
+ .adjfreq = ptp_gianfar_adjfreq,
+ .adjtime = ptp_gianfar_adjtime,
+ .gettime = ptp_gianfar_gettime,
+ .settime = ptp_gianfar_settime,
+ .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 platform_device *dev,
+ const struct of_device_id *match)
+{
+ struct device_node *node = dev->dev.of_node;
+ struct etsects *etsects = &the_clock;
+ struct timespec now;
+ u32 tmr_ctrl;
+
+ etsects->caps = ptp_gianfar_caps;
+
+ 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) ||
+ get_of_u32(node, "max-adj", &etsects->caps.max_adj)) {
+ 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;
+ }
+ etsects->regs = of_iomap(node, 0);
+ if (!etsects->regs) {
+ pr_err("of_iomap ptp registers failed\n");
+ return -EINVAL;
+ }
+ getnstimeofday(&now);
+ ptp_gianfar_settime(&etsects->caps, &now);
+
+ tmr_ctrl =
+ (etsects->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT |
+ (etsects->cksel & CKSEL_MASK) << CKSEL_SHIFT;
+
+ 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(&etsects->caps);
+
+ return IS_ERR(etsects->clock) ? PTR_ERR(etsects->clock) : 0;
+}
+
+static int gianfar_ptp_remove(struct platform_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 int __init ptp_gianfar_init(void)
+{
+ return of_register_platform_driver(&gianfar_ptp_driver);
+}
+
+module_init(ptp_gianfar_init);
+
+static void __exit ptp_gianfar_exit(void)
+{
+ of_unregister_platform_driver(&gianfar_ptp_driver);
+}
+
+module_exit(ptp_gianfar_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>");
+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 17be208..a4df298 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -24,4 +24,17 @@ config PTP_1588_CLOCK
To compile this driver as a module, choose M here: the module
will be called ptp.
+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.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 11/13] ptp: Added a clock that uses the eTSEC found on the MPC85xx.
@ 2010-12-31 19:16 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:16 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
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, one alarm, and the PPS callback.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
Documentation/powerpc/dts-bindings/fsl/tsec.txt | 57 +++
arch/powerpc/boot/dts/mpc8313erdb.dts | 14 +
arch/powerpc/boot/dts/mpc8572ds.dts | 14 +
arch/powerpc/boot/dts/p2020ds.dts | 14 +
arch/powerpc/boot/dts/p2020rdb.dts | 14 +
drivers/net/Makefile | 1 +
drivers/net/gianfar_ptp.c | 448 +++++++++++++++++++++++
drivers/net/gianfar_ptp_reg.h | 113 ++++++
drivers/ptp/Kconfig | 13 +
9 files changed, 688 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..f6edbb8 100644
--- a/Documentation/powerpc/dts-bindings/fsl/tsec.txt
+++ b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
@@ -74,3 +74,60 @@ 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.
+ - cksel 0= external clock, 1= eTSEC system clock, 3= RTC clock input.
+ Currently the driver only supports choice "1".
+ - tmr-fiper1 Fixed interval period pulse generator.
+ - tmr-fiper2 Fixed interval period pulse generator.
+ - max-adj Maximum frequency adjustment in parts per billion.
+
+ 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
+ max_adj = 1000000000 * (FreqDivRatio - 1.0) - 1
+
+ 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>;
+ max-adj = <659999998>;
+ };
diff --git a/arch/powerpc/boot/dts/mpc8313erdb.dts b/arch/powerpc/boot/dts/mpc8313erdb.dts
index 183f2aa..85a7eaa 100644
--- a/arch/powerpc/boot/dts/mpc8313erdb.dts
+++ b/arch/powerpc/boot/dts/mpc8313erdb.dts
@@ -208,6 +208,20 @@
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>;
+ max-adj = <659999998>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/mpc8572ds.dts b/arch/powerpc/boot/dts/mpc8572ds.dts
index cafc128..74208cd 100644
--- a/arch/powerpc/boot/dts/mpc8572ds.dts
+++ b/arch/powerpc/boot/dts/mpc8572ds.dts
@@ -324,6 +324,20 @@
};
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2 71 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xAAAAAAAB>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x3B9AC9FB>;
+ max-adj = <499999999>;
+ };
+
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..39d73bb 100644
--- a/arch/powerpc/boot/dts/p2020ds.dts
+++ b/arch/powerpc/boot/dts/p2020ds.dts
@@ -336,6 +336,20 @@
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>;
+ max-adj = <249999999>;
+ };
+
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..5498fb9 100644
--- a/arch/powerpc/boot/dts/p2020rdb.dts
+++ b/arch/powerpc/boot/dts/p2020rdb.dts
@@ -396,6 +396,20 @@
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>;
+ max-adj = <249999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90738d..c303f5f 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -31,6 +31,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..84fff15
--- /dev/null
+++ b/drivers/net/gianfar_ptp.c
@@ -0,0 +1,448 @@
+/*
+ * 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;
+ struct ptp_clock_info caps;
+ 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;
+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 (val & PP1) {
+ ack |= PP1;
+ event.type = PTP_CLOCK_PPS;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (ack) {
+ gfar_write(&etsects->regs->tmr_tevent, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_gianfar_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ u64 adj;
+ u32 diff, tmr_add;
+ int neg_adj = 0;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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(struct ptp_clock_info *ptp, s64 delta)
+{
+ s64 now;
+ unsigned long flags;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+
+ 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_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct etsects *etsects = container_of(ptp, struct etsects, caps);
+ unsigned long flags;
+ u32 bit, mask;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ switch (rq->extts.index) {
+ case 0:
+ bit = ETS1EN;
+ break;
+ case 1:
+ bit = ETS2EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= bit;
+ else
+ mask &= ~bit;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ case PTP_CLK_REQ_PPS:
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= PP1EN;
+ else
+ mask &= ~PP1EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ default:
+ break;
+ }
+
+ 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 = 1,
+ .adjfreq = ptp_gianfar_adjfreq,
+ .adjtime = ptp_gianfar_adjtime,
+ .gettime = ptp_gianfar_gettime,
+ .settime = ptp_gianfar_settime,
+ .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 platform_device *dev,
+ const struct of_device_id *match)
+{
+ struct device_node *node = dev->dev.of_node;
+ struct etsects *etsects = &the_clock;
+ struct timespec now;
+ u32 tmr_ctrl;
+
+ etsects->caps = ptp_gianfar_caps;
+
+ 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) ||
+ get_of_u32(node, "max-adj", &etsects->caps.max_adj)) {
+ 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;
+ }
+ etsects->regs = of_iomap(node, 0);
+ if (!etsects->regs) {
+ pr_err("of_iomap ptp registers failed\n");
+ return -EINVAL;
+ }
+ getnstimeofday(&now);
+ ptp_gianfar_settime(&etsects->caps, &now);
+
+ tmr_ctrl =
+ (etsects->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT |
+ (etsects->cksel & CKSEL_MASK) << CKSEL_SHIFT;
+
+ 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(&etsects->caps);
+
+ return IS_ERR(etsects->clock) ? PTR_ERR(etsects->clock) : 0;
+}
+
+static int gianfar_ptp_remove(struct platform_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 int __init ptp_gianfar_init(void)
+{
+ return of_register_platform_driver(&gianfar_ptp_driver);
+}
+
+module_init(ptp_gianfar_init);
+
+static void __exit ptp_gianfar_exit(void)
+{
+ of_unregister_platform_driver(&gianfar_ptp_driver);
+}
+
+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 17be208..a4df298 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -24,4 +24,17 @@ config PTP_1588_CLOCK
To compile this driver as a module, choose M here: the module
will be called ptp.
+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.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread
* [PATCH V8 12/13] ptp: Added a clock driver for the IXP46x.
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:17 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:17 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
This patch adds a driver for the hardware time stamping unit found on the
IXP465. The basic clock operations and an external trigger are implemented.
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 ++++++
drivers/net/arm/ixp4xx_eth.c | 191 ++++++++++++++
drivers/ptp/Kconfig | 13 +
drivers/ptp/Makefile | 1 +
drivers/ptp/ptp_ixp46x.c | 344 +++++++++++++++++++++++++
5 files changed, 627 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..729a6b2
--- /dev/null
+++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
@@ -0,0 +1,78 @@
+/*
+ * 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 */
+};
+
+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];
+};
+
+/* 0x00 Time Sync Control Register Bits */
+#define TSCR_AMM (1<<3)
+#define TSCR_ASM (1<<2)
+#define TSCR_TTM (1<<1)
+#define TSCR_RST (1<<0)
+
+/* 0x04 Time Sync Event Register Bits */
+#define TSER_SNM (1<<3)
+#define TSER_SNS (1<<2)
+#define TTIPEND (1<<1)
+
+/* 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 6028226..61cf4b4 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,171 @@ 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;
+ struct skb_shared_info *shtx;
+ u64 ns;
+ u32 ch, cnt, hi, lo, val;
+
+ shtx = skb_shinfo(skb);
+ if (unlikely(shtx->tx_flags & SKBTX_HW_TSTAMP && port->hwts_tx_en))
+ shtx->tx_flags |= SKBTX_IN_PROGRESS;
+ else
+ 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);
+ }
+ if (!(val & TX_SNAPSHOT_LOCKED)) {
+ shtx->tx_flags &= ~SKBTX_IN_PROGRESS;
+ 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 +751,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;
@@ -728,6 +907,10 @@ 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);
+ skb_tx_timestamp(skb);
+
return NETDEV_TX_OK;
}
@@ -783,6 +966,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 +1357,11 @@ static int __devinit eth_init_one(struct platform_device *pdev)
char phy_id[MII_BUS_ID_SIZE + 3];
int err;
+ if (ptp_filter_init(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 a4df298..6f1dcbf 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -37,4 +37,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 480e2af..f6933e8 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -4,3 +4,4 @@
ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o
obj-$(CONFIG_PTP_1588_CLOCK) += ptp.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..098ae24
--- /dev/null
+++ b/drivers/ptp/ptp_ixp46x.c
@@ -0,0 +1,344 @@
+/*
+ * 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/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/ptp_clock_kernel.h>
+#include <mach/ixp46x_ts.h>
+
+#define DRIVER "ptp_ixp46x"
+#define N_EXT_TS 2
+#define MASTER_GPIO 8
+#define MASTER_IRQ 25
+#define SLAVE_GPIO 7
+#define SLAVE_IRQ 24
+
+struct ixp_clock {
+ struct ixp46x_ts_regs *regs;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info caps;
+ int exts0_enabled;
+ int exts1_enabled;
+};
+
+DEFINE_SPINLOCK(register_lock);
+
+/*
+ * Register access functions
+ */
+
+static inline u32 ixp_read(volatile unsigned __iomem *addr)
+{
+ u32 val;
+ val = __raw_readl(addr);
+ return val;
+}
+
+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)
+{
+ u32 hi, lo;
+
+ ns >>= TICKS_NS_SHIFT;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+
+ ixp_write(®s->SysTimeLo, lo);
+ ixp_write(®s->SysTimeHi, hi);
+}
+
+/*
+ * Interrupt service routine
+ */
+
+static irqreturn_t isr(int irq, void *priv)
+{
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+ struct ptp_clock_event event;
+ u32 ack = 0, lo, hi, val;
+
+ val = ixp_read(®s->Event);
+
+ if (val & TSER_SNS) {
+ ack |= TSER_SNS;
+ if (ixp_clock->exts0_enabled) {
+ hi = ixp_read(®s->ASMSHi);
+ lo = ixp_read(®s->ASMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TSER_SNM) {
+ ack |= TSER_SNM;
+ if (ixp_clock->exts1_enabled) {
+ hi = ixp_read(®s->AMMSHi);
+ lo = ixp_read(®s->AMMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 1;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TTIPEND)
+ ack |= TTIPEND; /* this bit seems to be always set */
+
+ if (ack) {
+ ixp_write(®s->Event, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ u64 adj;
+ u32 diff, addend;
+ int neg_adj = 0;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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(struct ptp_clock_info *ptp, s64 delta)
+{
+ s64 now;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ switch (rq->extts.index) {
+ case 0:
+ ixp_clock->exts0_enabled = on ? 1 : 0;
+ break;
+ case 1:
+ ixp_clock->exts1_enabled = on ? 1 : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_ixp_caps = {
+ .owner = THIS_MODULE,
+ .name = "IXP46X timer",
+ .max_adj = 66666655,
+ .n_ext_ts = N_EXT_TS,
+ .pps = 0,
+ .adjfreq = ptp_ixp_adjfreq,
+ .adjtime = ptp_ixp_adjtime,
+ .gettime = ptp_ixp_gettime,
+ .settime = ptp_ixp_settime,
+ .enable = ptp_ixp_enable,
+};
+
+/* module operations */
+
+static struct ixp_clock ixp_clock;
+
+static int setup_interrupt(int gpio)
+{
+ int irq;
+
+ gpio_line_config(gpio, IXP4XX_GPIO_IN);
+
+ irq = gpio_to_irq(gpio);
+
+ if (NO_IRQ == irq)
+ return NO_IRQ;
+
+ if (set_irq_type(irq, IRQF_TRIGGER_FALLING)) {
+ pr_err("cannot set trigger type for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) {
+ pr_err("request_irq failed for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ return irq;
+}
+
+static void __exit ptp_ixp_exit(void)
+{
+ free_irq(MASTER_IRQ, &ixp_clock);
+ free_irq(SLAVE_IRQ, &ixp_clock);
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+}
+
+static int __init ptp_ixp_init(void)
+{
+ if (!cpu_is_ixp46x())
+ return -ENODEV;
+
+ ixp_clock.regs =
+ (struct ixp46x_ts_regs __iomem *)IXP4XX_TIMESYNC_BASE_VIRT;
+
+ ixp_clock.caps = ptp_ixp_caps;
+
+ ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps);
+
+ if (IS_ERR(ixp_clock.ptp_clock))
+ return PTR_ERR(ixp_clock.ptp_clock);
+
+ ixp_write(&ixp_clock.regs->Addend, DEFAULT_ADDEND);
+ ixp_write(&ixp_clock.regs->TrgtLo, 1);
+ ixp_write(&ixp_clock.regs->TrgtHi, 0);
+ ixp_write(&ixp_clock.regs->Event, TTIPEND);
+
+ if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO);
+ goto no_master;
+ }
+ if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO);
+ goto no_slave;
+ }
+
+ return 0;
+no_slave:
+ free_irq(MASTER_IRQ, &ixp_clock);
+no_master:
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+ return -ENODEV;
+}
+
+module_init(ptp_ixp_init);
+module_exit(ptp_ixp_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>");
+MODULE_DESCRIPTION("PTP clock using the IXP46X timer");
+MODULE_LICENSE("GPL");
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 12/13] ptp: Added a clock driver for the IXP46x.
@ 2010-12-31 19:17 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:17 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
This patch adds a driver for the hardware time stamping unit found on the
IXP465. The basic clock operations and an external trigger are implemented.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 ++++++
drivers/net/arm/ixp4xx_eth.c | 191 ++++++++++++++
drivers/ptp/Kconfig | 13 +
drivers/ptp/Makefile | 1 +
drivers/ptp/ptp_ixp46x.c | 344 +++++++++++++++++++++++++
5 files changed, 627 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..729a6b2
--- /dev/null
+++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
@@ -0,0 +1,78 @@
+/*
+ * 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 */
+};
+
+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];
+};
+
+/* 0x00 Time Sync Control Register Bits */
+#define TSCR_AMM (1<<3)
+#define TSCR_ASM (1<<2)
+#define TSCR_TTM (1<<1)
+#define TSCR_RST (1<<0)
+
+/* 0x04 Time Sync Event Register Bits */
+#define TSER_SNM (1<<3)
+#define TSER_SNS (1<<2)
+#define TTIPEND (1<<1)
+
+/* 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 6028226..61cf4b4 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,171 @@ 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;
+ struct skb_shared_info *shtx;
+ u64 ns;
+ u32 ch, cnt, hi, lo, val;
+
+ shtx = skb_shinfo(skb);
+ if (unlikely(shtx->tx_flags & SKBTX_HW_TSTAMP && port->hwts_tx_en))
+ shtx->tx_flags |= SKBTX_IN_PROGRESS;
+ else
+ 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);
+ }
+ if (!(val & TX_SNAPSHOT_LOCKED)) {
+ shtx->tx_flags &= ~SKBTX_IN_PROGRESS;
+ 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 +751,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;
@@ -728,6 +907,10 @@ 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);
+ skb_tx_timestamp(skb);
+
return NETDEV_TX_OK;
}
@@ -783,6 +966,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 +1357,11 @@ static int __devinit eth_init_one(struct platform_device *pdev)
char phy_id[MII_BUS_ID_SIZE + 3];
int err;
+ if (ptp_filter_init(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 a4df298..6f1dcbf 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -37,4 +37,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 480e2af..f6933e8 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -4,3 +4,4 @@
ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o
obj-$(CONFIG_PTP_1588_CLOCK) += ptp.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..098ae24
--- /dev/null
+++ b/drivers/ptp/ptp_ixp46x.c
@@ -0,0 +1,344 @@
+/*
+ * 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/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/ptp_clock_kernel.h>
+#include <mach/ixp46x_ts.h>
+
+#define DRIVER "ptp_ixp46x"
+#define N_EXT_TS 2
+#define MASTER_GPIO 8
+#define MASTER_IRQ 25
+#define SLAVE_GPIO 7
+#define SLAVE_IRQ 24
+
+struct ixp_clock {
+ struct ixp46x_ts_regs *regs;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info caps;
+ int exts0_enabled;
+ int exts1_enabled;
+};
+
+DEFINE_SPINLOCK(register_lock);
+
+/*
+ * Register access functions
+ */
+
+static inline u32 ixp_read(volatile unsigned __iomem *addr)
+{
+ u32 val;
+ val = __raw_readl(addr);
+ return val;
+}
+
+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)
+{
+ u32 hi, lo;
+
+ ns >>= TICKS_NS_SHIFT;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+
+ ixp_write(®s->SysTimeLo, lo);
+ ixp_write(®s->SysTimeHi, hi);
+}
+
+/*
+ * Interrupt service routine
+ */
+
+static irqreturn_t isr(int irq, void *priv)
+{
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+ struct ptp_clock_event event;
+ u32 ack = 0, lo, hi, val;
+
+ val = ixp_read(®s->Event);
+
+ if (val & TSER_SNS) {
+ ack |= TSER_SNS;
+ if (ixp_clock->exts0_enabled) {
+ hi = ixp_read(®s->ASMSHi);
+ lo = ixp_read(®s->ASMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TSER_SNM) {
+ ack |= TSER_SNM;
+ if (ixp_clock->exts1_enabled) {
+ hi = ixp_read(®s->AMMSHi);
+ lo = ixp_read(®s->AMMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 1;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TTIPEND)
+ ack |= TTIPEND; /* this bit seems to be always set */
+
+ if (ack) {
+ ixp_write(®s->Event, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ u64 adj;
+ u32 diff, addend;
+ int neg_adj = 0;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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(struct ptp_clock_info *ptp, s64 delta)
+{
+ s64 now;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ 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_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ switch (rq->extts.index) {
+ case 0:
+ ixp_clock->exts0_enabled = on ? 1 : 0;
+ break;
+ case 1:
+ ixp_clock->exts1_enabled = on ? 1 : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_ixp_caps = {
+ .owner = THIS_MODULE,
+ .name = "IXP46X timer",
+ .max_adj = 66666655,
+ .n_ext_ts = N_EXT_TS,
+ .pps = 0,
+ .adjfreq = ptp_ixp_adjfreq,
+ .adjtime = ptp_ixp_adjtime,
+ .gettime = ptp_ixp_gettime,
+ .settime = ptp_ixp_settime,
+ .enable = ptp_ixp_enable,
+};
+
+/* module operations */
+
+static struct ixp_clock ixp_clock;
+
+static int setup_interrupt(int gpio)
+{
+ int irq;
+
+ gpio_line_config(gpio, IXP4XX_GPIO_IN);
+
+ irq = gpio_to_irq(gpio);
+
+ if (NO_IRQ == irq)
+ return NO_IRQ;
+
+ if (set_irq_type(irq, IRQF_TRIGGER_FALLING)) {
+ pr_err("cannot set trigger type for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) {
+ pr_err("request_irq failed for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ return irq;
+}
+
+static void __exit ptp_ixp_exit(void)
+{
+ free_irq(MASTER_IRQ, &ixp_clock);
+ free_irq(SLAVE_IRQ, &ixp_clock);
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+}
+
+static int __init ptp_ixp_init(void)
+{
+ if (!cpu_is_ixp46x())
+ return -ENODEV;
+
+ ixp_clock.regs =
+ (struct ixp46x_ts_regs __iomem *)IXP4XX_TIMESYNC_BASE_VIRT;
+
+ ixp_clock.caps = ptp_ixp_caps;
+
+ ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps);
+
+ if (IS_ERR(ixp_clock.ptp_clock))
+ return PTR_ERR(ixp_clock.ptp_clock);
+
+ ixp_write(&ixp_clock.regs->Addend, DEFAULT_ADDEND);
+ ixp_write(&ixp_clock.regs->TrgtLo, 1);
+ ixp_write(&ixp_clock.regs->TrgtHi, 0);
+ ixp_write(&ixp_clock.regs->Event, TTIPEND);
+
+ if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO);
+ goto no_master;
+ }
+ if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO);
+ goto no_slave;
+ }
+
+ return 0;
+no_slave:
+ free_irq(MASTER_IRQ, &ixp_clock);
+no_master:
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+ return -ENODEV;
+}
+
+module_init(ptp_ixp_init);
+module_exit(ptp_ixp_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the IXP46X timer");
+MODULE_LICENSE("GPL");
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* Re: [PATCH V8 12/13] ptp: Added a clock driver for the IXP46x.
2010-12-31 19:17 ` Richard Cochran
(?)
@ 2011-01-06 21:01 ` Krzysztof Halasa
[not found] ` <m3ei8plpa0.fsf-gTjgKJgtlHj77SC2UrCW1FMQynFLKtET@public.gmane.org>
-1 siblings, 1 reply; 76+ messages in thread
From: Krzysztof Halasa @ 2011-01-06 21:01 UTC (permalink / raw)
To: Richard Cochran
Cc: linux-kernel, linux-api, netdev, Alan Cox, Arnd Bergmann,
Christoph Lameter, David Miller, John Stultz, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
Richard Cochran <richardcochran@gmail.com> writes:
> +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 */
I don't like these XxxYyyZzz either :-(
> +static void do_tx_timestamp(struct port *port, struct sk_buff *skb)
> +{
> +#ifdef __ARMEB__
> + struct skb_shared_hwtstamps shhwtstamps;
> + struct ixp46x_ts_regs *regs;
> + struct skb_shared_info *shtx;
> + u64 ns;
> + u32 ch, cnt, hi, lo, val;
> +
> + shtx = skb_shinfo(skb);
> + if (unlikely(shtx->tx_flags & SKBTX_HW_TSTAMP && port->hwts_tx_en))
> + shtx->tx_flags |= SKBTX_IN_PROGRESS;
> + else
> + 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);
> + }
> + if (!(val & TX_SNAPSHOT_LOCKED)) {
> + shtx->tx_flags &= ~SKBTX_IN_PROGRESS;
> + 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
> +}
And what if we're little-endian? Why does it depend on BE?
> @@ -1171,6 +1357,11 @@ static int __devinit eth_init_one(struct platform_device *pdev)
> char phy_id[MII_BUS_ID_SIZE + 3];
> int err;
>
> + if (ptp_filter_init(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;
Shouldn't it depend on CPU type?
BTW which CPU is required? IXP46x (455/460/465)? Does it work on 43x?
> + if (NO_IRQ == irq)
> + return NO_IRQ;
Don't like these either :-(
Not showstoppers but...
Also I don't like the ixp_read/ixp_write() trivial macros. Why not
simply call __raw_readl() and __raw_writel()?
--
Krzysztof Halasa
^ permalink raw reply [flat|nested] 76+ messages in thread
* [PATCH V8 13/13] ptp: Added a clock driver for the National Semiconductor PHYTER.
2010-12-31 19:11 ` Richard Cochran
@ 2010-12-31 19:17 ` Richard Cochran
-1 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:17 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-api-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
Alan Cox, Arnd Bergmann, Christoph Lameter, David Miller,
John Stultz, Krzysztof Halasa, Peter Zijlstra, Rodolfo Giometti,
Thomas Gleixner
This patch adds support for the PTP clock found on the DP83640.
The basic clock operations and one external time stamp have
been implemented.
Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
---
drivers/net/phy/Kconfig | 29 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dp83640.c | 896 +++++++++++++++++++++++++++++++++++++++++
drivers/net/phy/dp83640_reg.h | 261 ++++++++++++
4 files changed, 1187 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/phy/dp83640.c
create mode 100644 drivers/net/phy/dp83640_reg.h
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 35fda5a..1f1399a 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -76,6 +76,35 @@ config NATIONAL_PHY
---help---
Currently supports the DP83865 PHY.
+config DP83640_PHY
+ tristate "Driver for the National Semiconductor DP83640 PHYTER"
+ depends on PTP_1588_CLOCK
+ depends on NETWORK_PHY_TIMESTAMPING
+ ---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 skb_tx_timetamp() function.
+
+config DP83640_PHY_STATUS_FRAMES
+ bool "DP83640 Status Frames"
+ default y
+ depends on DP83640_PHY
+ ---help---
+ This option allows the DP83640 PHYTER driver to obtain time
+ stamps from the PHY via special status frames, rather than
+ reading over the MDIO bus. Using status frames is therefore
+ more efficient. However, if enabled, this option will cause
+ the driver to add a mutlicast address to the MAC.
+
+ Say Y here, unless your MAC does not support multicast
+ destination addresses.
+
config STE10XP
depends on PHYLIB
tristate "Driver for STMicroelectronics STe10Xp PHYs"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 13bebab..2333215 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_FIXED_PHY) += fixed.o
obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
obj-$(CONFIG_NATIONAL_PHY) += national.o
+obj-$(CONFIG_DP83640_PHY) += dp83640.o
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_MICREL_PHY) += micrel.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
new file mode 100644
index 0000000..5860d66
--- /dev/null
+++ b/drivers/net/phy/dp83640.c
@@ -0,0 +1,896 @@
+/*
+ * Driver for the National Semiconductor DP83640 PHYTER
+ *
+ * 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/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+
+#include "dp83640_reg.h"
+
+#ifdef CONFIG_DP83640_PHY_STATUS_FRAMES
+#define USE_STATUS_FRAMES
+#endif
+
+#define DP83640_PHY_ID 0x20005ce1
+#define PAGESEL 0x13
+#define LAYER4 0x02
+#define LAYER2 0x01
+#define MAX_RXTS 4
+#define MAX_TXTS 4
+#define N_EXT_TS 1
+#define PSF_PTPVER 2
+#define PSF_EVNT 0x4000
+#define PSF_RX 0x2000
+#define PSF_TX 0x1000
+#define EXT_EVENT 1
+#define EXT_GPIO 1
+
+#if defined(__BIG_ENDIAN)
+#define ENDIAN_FLAG 0
+#elif defined(__LITTLE_ENDIAN)
+#define ENDIAN_FLAG PSF_ENDIAN
+#endif
+
+#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
+
+struct phy_rxts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+ u16 seqid; /* sequenceId[15:0] */
+ u16 msgtype; /* messageType[3:0], hash[11:0] */
+};
+
+struct phy_txts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+};
+
+struct rxts {
+ struct list_head list;
+ unsigned long tmo;
+ u64 ns;
+ u16 seqid;
+ u8 msgtype;
+ u16 hash;
+};
+
+struct dp83640_private {
+ struct ptp_clock_info caps;
+ struct phy_device *phydev;
+ struct work_struct ts_work;
+ int hwts_tx_en;
+ int hwts_rx_en;
+ int layer;
+ int version;
+ /* protects extended registers from concurrent access */
+ struct mutex extreg_mux;
+ int page;
+ /* remember the last event time stamp */
+ struct phy_txts edata;
+ /* list of rx timestamps */
+ struct list_head rxts;
+ struct list_head rxpool;
+ struct rxts rx_pool_data[MAX_RXTS];
+ /* protects above three fields from concurrent access */
+ spinlock_t rx_lock;
+
+ struct sk_buff_head rx_queue;
+ struct sk_buff_head tx_queue;
+};
+
+/* globals */
+
+static struct ptp_clock *dp83640_clock;
+DEFINE_MUTEX(clock_lock); /* protects the one and only dp83640_clock */
+
+static void do_timestamp_work(struct work_struct *work);
+
+/* extended register access functions */
+
+static int ext_read(struct phy_device *phydev, int page, u32 regnum)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ int val;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ val = phy_read(phydev, regnum);
+
+ return val;
+}
+
+static void ext_write(struct phy_device *phydev, int page, u32 regnum, u16 val)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ phy_write(phydev, regnum, val);
+}
+
+static int tdr_write(struct phy_device *phydev, const struct timespec *ts, u16 cmd)
+{
+ 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);
+
+ return 0;
+}
+
+/* convert phy timestamps into driver timestamps */
+
+static void phy2rxts(struct phy_rxts *p, struct rxts *rxts)
+{
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ rxts->ns = p->ns_lo;
+ rxts->ns |= (p->ns_hi & 0x3fff) << 16;
+ rxts->ns += ((u64)sec) * 1000000000ULL;
+ rxts->seqid = p->seqid;
+ rxts->msgtype = (p->msgtype >> 12) & 0xf;
+ rxts->hash = p->msgtype & 0x0fff;
+}
+
+static u64 phy2txts(struct phy_txts *p)
+{
+ u64 ns;
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ ns = p->ns_lo;
+ ns |= (p->ns_hi & 0x3fff) << 16;
+ ns += ((u64)sec) * 1000000000ULL;
+
+ return ns;
+}
+
+/* ptp clock methods */
+
+static int ptp_dp83640_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct phy_device *phydev = dp83640->phydev;
+ u64 rate;
+ int neg_adj = 0;
+ u16 hi, lo;
+
+ 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;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ ext_write(phydev, PAGE4, PTP_RATEH, hi);
+ ext_write(phydev, PAGE4, PTP_RATEL, lo);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return 0;
+}
+
+static int ptp_dp83640_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct timespec ts;
+ int err;
+
+ ts = ns_to_timespec(delta);
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, &ts, PTP_STEP_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct phy_device *phydev = dp83640->phydev;
+ unsigned int val[4];
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ 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] */
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ ts->tv_nsec = val[0] | (val[1] << 16);
+ ts->tv_sec = val[2] | (val[3] << 16);
+
+ return 0;
+}
+
+static int ptp_dp83640_settime(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ int err;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, ts, PTP_LOAD_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct phy_device *phydev = dp83640->phydev;
+ u16 evnt;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ if (rq->extts.index != 0)
+ return -EINVAL;
+ evnt = EVNT_WR | (EXT_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+ if (on) {
+ evnt |= (EXT_GPIO & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+ evnt |= EVNT_RISE;
+ }
+ ext_write(phydev, PAGE5, PTP_EVNT, evnt);
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info dp83640_caps = {
+ .owner = THIS_MODULE,
+ .name = "dp83640 timer",
+ .max_adj = 1953124,
+ .n_alarm = 0,
+ .n_ext_ts = N_EXT_TS,
+ .n_per_out = 0,
+ .pps = 0,
+ .adjfreq = ptp_dp83640_adjfreq,
+ .adjtime = ptp_dp83640_adjtime,
+ .gettime = ptp_dp83640_gettime,
+ .settime = ptp_dp83640_settime,
+ .enable = ptp_dp83640_enable,
+};
+
+/* status frame conditional code */
+
+#ifdef USE_STATUS_FRAMES
+
+static u8 status_frame_dst[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 };
+static u8 status_frame_src[6] = { 0x08, 0x00, 0x17, 0x0B, 0x6B, 0x0F };
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+ u16 cfg0 = 0, ver;
+
+ if (on)
+ cfg0 = PSF_EVNT_EN | PSF_RXTS_EN | PSF_TXTS_EN | ENDIAN_FLAG;
+
+ ver = (PSF_PTPVER & VERSIONPTP_MASK) << VERSIONPTP_SHIFT;
+
+ ext_write(phydev, PAGE5, PSF_CFG0, cfg0);
+ ext_write(phydev, PAGE6, PSF_CFG1, ver);
+
+ if (!phydev->attached_dev) {
+ pr_err("expected to find an attached netdevice\n");
+ BUG();
+ }
+
+ if (on) {
+ if (dev_mc_add(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to add mc address\n");
+ } else {
+ if (dev_mc_del(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to delete mc address\n");
+ }
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ struct ethhdr *h = eth_hdr(skb);
+
+ if (PTP_CLASS_V2_L2 == type &&
+ !memcmp(h->h_source, status_frame_src, sizeof(status_frame_src)))
+ return true;
+ else
+ return false;
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+}
+
+#else /* USE_STATUS_FRAMES */
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ return false;
+}
+
+static void read_rxts(struct phy_device *phydev, struct rxts *rxts)
+{
+ struct phy_rxts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.seqid = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.msgtype = ext_read(phydev, PAGE4, PTP_RXTS);
+
+ rxts->tmo = jiffies + HZ;
+ phy2rxts(&p, rxts);
+}
+
+static u64 read_txts(struct phy_device *phydev)
+{
+ struct phy_txts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+
+ return phy2txts(&p);
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+ struct rxts *rxts;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ for (;;) {
+ int val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & RXTS_RDY))
+ break;
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ break;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ read_rxts(dp83640->phydev, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+ int val;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ while ((skb = skb_dequeue(&dp83640->tx_queue)) != NULL) {
+
+ val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & TXTS_RDY)) {
+ skb_queue_head(&dp83640->tx_queue, skb);
+ break;
+ }
+ ns = read_txts(dp83640->phydev);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+#endif /* !USE_STATUS_FRAMES */
+
+/* time stamping methods */
+
+static void decode_evnt(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts, u16 ests)
+{
+ struct ptp_clock_event event;
+ int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK;
+
+ switch (words) { /* fall through in every case */
+ case 3:
+ dp83640->edata.sec_hi = phy_txts->sec_hi;
+ case 2:
+ dp83640->edata.sec_lo = phy_txts->sec_lo;
+ case 1:
+ dp83640->edata.ns_hi = phy_txts->ns_hi;
+ case 0:
+ dp83640->edata.ns_lo = phy_txts->ns_lo;
+ }
+
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = phy2txts(&dp83640->edata);
+
+ ptp_clock_event(dp83640_clock, &event);
+}
+
+static void decode_rxts(struct dp83640_private *dp83640,
+ struct phy_rxts *phy_rxts)
+{
+ struct rxts *rxts;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ goto out;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ phy2rxts(phy_rxts, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+out:
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void decode_txts(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+
+ /* We must already have the skb that triggered this. */
+
+ skb = skb_dequeue(&dp83640->tx_queue);
+
+ if (!skb) {
+ pr_warning("dp83640: have timestamp but tx_queue empty\n");
+ return;
+ }
+ ns = phy2txts(phy_txts);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+}
+
+static void decode_status_frame(struct dp83640_private *dp83640,
+ struct sk_buff *skb)
+{
+ struct phy_rxts *phy_rxts;
+ struct phy_txts *phy_txts;
+ u8 *ptr;
+ int len, size;
+ u16 ests, type;
+
+ ptr = skb->data + 2;
+
+ for (len = skb_headlen(skb) - 2; len > sizeof(type); len -= size) {
+
+ type = *(u16 *)ptr;
+ ests = type & 0x0fff;
+ type = type & 0xf000;
+ len -= sizeof(type);
+ ptr += sizeof(type);
+
+ if (PSF_RX == type && len >= sizeof(*phy_rxts)) {
+
+ phy_rxts = (struct phy_rxts *) ptr;
+ decode_rxts(dp83640, phy_rxts);
+ size = sizeof(*phy_rxts);
+
+ } else if (PSF_TX == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_txts(dp83640, phy_txts);
+ size = sizeof(*phy_txts);
+
+ } else if (PSF_EVNT == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_evnt(dp83640, phy_txts, ests);
+ size = sizeof(*phy_txts);
+
+ } else {
+ size = 0;
+ break;
+ }
+ ptr += size;
+ }
+}
+
+static int expired(struct rxts *rxts)
+{
+ return time_after(jiffies, rxts->tmo);
+}
+
+static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
+{
+ u16 *seqid;
+ u8 *msgtype, *data = skb_mac_header(skb);
+
+ /* check sequenceID, messageType, 12 bit hash of offset 20-29 */
+ /* We assume that the IPv4 header has no options. */
+
+ switch (type) {
+ case PTP_CLASS_V1_IPV4:
+ msgtype = data + 42 + 32;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V1_IPV6:
+ msgtype = data + 62 + 32;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_IPV4:
+ msgtype = data + 42 + 0;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V2_IPV6:
+ msgtype = data + 62 + 0;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_L2:
+ msgtype = data + 14 + 0;
+ seqid = (u16 *)(data + 14 + 30);
+ break;
+ case PTP_CLASS_V2_VLAN:
+ msgtype = data + 18 + 0;
+ seqid = (u16 *)(data + 18 + 30);
+ break;
+ default:
+ return 0;
+ }
+
+ return ((*msgtype & 0xf) == rxts->msgtype && *seqid == rxts->seqid);
+}
+
+static int dp83640_probe(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640;
+ int i;
+
+ dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
+ if (!dp83640)
+ return -ENOMEM;
+
+ dp83640->phydev = phydev;
+ INIT_WORK(&dp83640->ts_work, do_timestamp_work);
+ mutex_init(&dp83640->extreg_mux);
+
+ INIT_LIST_HEAD(&dp83640->rxts);
+ INIT_LIST_HEAD(&dp83640->rxpool);
+ for (i = 0; i < MAX_RXTS; i++)
+ list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
+
+ phydev->priv = dp83640;
+
+ spin_lock_init(&dp83640->rx_lock);
+ skb_queue_head_init(&dp83640->rx_queue);
+ skb_queue_head_init(&dp83640->tx_queue);
+
+ mutex_lock(&clock_lock);
+
+ if (!dp83640_clock) {
+ dp83640->caps = dp83640_caps;
+ dp83640_clock = ptp_clock_register(&dp83640->caps);
+ if (IS_ERR(dp83640_clock)) {
+ mutex_unlock(&clock_lock);
+ kfree(dp83640);
+ return PTR_ERR(dp83640_clock);
+ }
+ }
+ mutex_unlock(&clock_lock);
+
+ return 0;
+}
+
+static void dp83640_remove(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ enable_status_frames(phydev, false);
+
+ cancel_work_sync(&dp83640->ts_work);
+
+ mutex_lock(&clock_lock);
+
+ if (dp83640->caps.owner) {
+ ptp_clock_unregister(dp83640_clock);
+ dp83640_clock = NULL;
+ }
+ mutex_unlock(&clock_lock);
+
+ mutex_destroy(&dp83640->extreg_lock);
+
+ kfree(dp83640);
+}
+
+static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ struct hwtstamp_config cfg;
+ u16 txcfg0, rxcfg0;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.flags) /* reserved for future extensions */
+ return -EINVAL;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ dp83640->hwts_tx_en = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ dp83640->hwts_tx_en = 1;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ dp83640->hwts_rx_en = 0;
+ dp83640->layer = 0;
+ dp83640->version = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 1;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER2;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4|LAYER2;
+ dp83640->version = 2;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ txcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+ rxcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+
+ if (dp83640->layer & LAYER2) {
+ txcfg0 |= TX_L2_EN;
+ rxcfg0 |= RX_L2_EN;
+ }
+ if (dp83640->layer & LAYER4) {
+ txcfg0 |= TX_IPV6_EN | TX_IPV4_EN;
+ rxcfg0 |= RX_IPV6_EN | RX_IPV4_EN;
+ }
+
+ if (dp83640->hwts_tx_en)
+ txcfg0 |= TX_TS_EN;
+
+ if (dp83640->hwts_rx_en)
+ rxcfg0 |= RX_TS_EN;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ if (dp83640->hwts_tx_en || dp83640->hwts_rx_en) {
+ enable_status_frames(phydev, true);
+ ext_write(phydev, PAGE4, PTP_CTL, PTP_ENABLE);
+ }
+
+ ext_write(phydev, PAGE5, PTP_TXCFG0, txcfg0);
+ ext_write(phydev, PAGE5, PTP_RXCFG0, rxcfg0);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static void rx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct list_head *this, *next;
+ struct rxts *rxts;
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct sk_buff *skb;
+ unsigned int type;
+ unsigned long flags;
+
+ /* Deliver each deferred packet, with or without a time stamp. */
+
+ while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL) {
+ type = SKB_PTP_TYPE(skb);
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (match(skb, type, rxts)) {
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns);
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+ netif_rx(skb);
+ }
+
+ /* Clear out expired time stamps. */
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (expired(rxts)) {
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void do_timestamp_work(struct work_struct *work)
+{
+ struct dp83640_private *dp83640 =
+ container_of(work, struct dp83640_private, ts_work);
+
+ rx_reading_work(dp83640);
+ rx_timestamp_work(dp83640);
+ tx_timestamp_work(dp83640);
+}
+
+static bool dp83640_rxtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_rx_en)
+ return false;
+
+ if (is_status_frame(skb, type)) {
+ decode_status_frame(dp83640, skb);
+ /* Let the stack drop this frame. */
+ return false;
+ }
+
+ SKB_PTP_TYPE(skb) = type;
+ skb_queue_tail(&dp83640->rx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+
+ return true;
+}
+
+static void dp83640_txtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_tx_en) {
+ kfree_skb(skb);
+ return;
+ }
+ skb_queue_tail(&dp83640->tx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+}
+
+static struct phy_driver dp83640_driver = {
+ .phy_id = DP83640_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "NatSemi DP83640",
+ .features = PHY_BASIC_FEATURES,
+ .flags = 0,
+ .probe = dp83640_probe,
+ .remove = dp83640_remove,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .hwtstamp = dp83640_hwtstamp,
+ .rxtstamp = dp83640_rxtstamp,
+ .txtstamp = dp83640_txtstamp,
+ .driver = {.owner = THIS_MODULE,}
+};
+
+static int __init dp83640_init(void)
+{
+ return phy_driver_register(&dp83640_driver);
+}
+
+static void __exit dp83640_exit(void)
+{
+ phy_driver_unregister(&dp83640_driver);
+}
+
+MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver");
+MODULE_AUTHOR("Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>");
+MODULE_LICENSE("GPL");
+
+module_init(dp83640_init);
+module_exit(dp83640_exit);
+
+static struct mdio_device_id dp83640_tbl[] = {
+ { DP83640_PHY_ID, 0xfffffff0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, dp83640_tbl);
diff --git a/drivers/net/phy/dp83640_reg.h b/drivers/net/phy/dp83640_reg.h
new file mode 100644
index 0000000..9d34bb6
--- /dev/null
+++ b/drivers/net/phy/dp83640_reg.h
@@ -0,0 +1,261 @@
+/* dp83640_reg.h
+ * Generated by regen.tcl on Fri Jul 23 09:45:18 AM CEST 2010
+ */
+#ifndef HAVE_DP83640_REGISTERS
+#define HAVE_DP83640_REGISTERS
+
+#define PAGE4 0x0004
+#define PTP_CTL 0x0014 /* PTP Control Register */
+#define PTP_TDR 0x0015 /* PTP Time Data Register */
+#define PTP_STS 0x0016 /* PTP Status Register */
+#define PTP_TSTS 0x0017 /* PTP Trigger Status Register */
+#define PTP_RATEL 0x0018 /* PTP Rate Low Register */
+#define PTP_RATEH 0x0019 /* PTP Rate High Register */
+#define PTP_RDCKSUM 0x001a /* PTP Read Checksum */
+#define PTP_WRCKSUM 0x001b /* PTP Write Checksum */
+#define PTP_TXTS 0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */
+#define PTP_RXTS 0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */
+#define PTP_ESTS 0x001e /* PTP Event Status Register */
+#define PTP_EDATA 0x001f /* PTP Event Data Register */
+
+#define PAGE5 0x0005
+#define PTP_TRIG 0x0014 /* PTP Trigger Configuration Register */
+#define PTP_EVNT 0x0015 /* PTP Event Configuration Register */
+#define PTP_TXCFG0 0x0016 /* PTP Transmit Configuration Register 0 */
+#define PTP_TXCFG1 0x0017 /* PTP Transmit Configuration Register 1 */
+#define PSF_CFG0 0x0018 /* PHY Status Frame Configuration Register 0 */
+#define PTP_RXCFG0 0x0019 /* PTP Receive Configuration Register 0 */
+#define PTP_RXCFG1 0x001a /* PTP Receive Configuration Register 1 */
+#define PTP_RXCFG2 0x001b /* PTP Receive Configuration Register 2 */
+#define PTP_RXCFG3 0x001c /* PTP Receive Configuration Register 3 */
+#define PTP_RXCFG4 0x001d /* PTP Receive Configuration Register 4 */
+#define PTP_TRDL 0x001e /* PTP Temporary Rate Duration Low Register */
+#define PTP_TRDH 0x001f /* PTP Temporary Rate Duration High Register */
+
+#define PAGE6 0x0006
+#define PTP_COC 0x0014 /* PTP Clock Output Control Register */
+#define PSF_CFG1 0x0015 /* PHY Status Frame Configuration Register 1 */
+#define PSF_CFG2 0x0016 /* PHY Status Frame Configuration Register 2 */
+#define PSF_CFG3 0x0017 /* PHY Status Frame Configuration Register 3 */
+#define PSF_CFG4 0x0018 /* PHY Status Frame Configuration Register 4 */
+#define PTP_SFDCFG 0x0019 /* PTP SFD Configuration Register */
+#define PTP_INTCTL 0x001a /* PTP Interrupt Control Register */
+#define PTP_CLKSRC 0x001b /* PTP Clock Source Register */
+#define PTP_ETR 0x001c /* PTP Ethernet Type Register */
+#define PTP_OFF 0x001d /* PTP Offset Register */
+#define PTP_GPIOMON 0x001e /* PTP GPIO Monitor Register */
+#define PTP_RXHASH 0x001f /* PTP Receive Hash Register */
+
+/* Bit definitions for the PTP_CTL register */
+#define TRIG_SEL_SHIFT (10) /* PTP Trigger Select */
+#define TRIG_SEL_MASK (0x7)
+#define TRIG_DIS (1<<9) /* Disable PTP Trigger */
+#define TRIG_EN (1<<8) /* Enable PTP Trigger */
+#define TRIG_READ (1<<7) /* Read PTP Trigger */
+#define TRIG_LOAD (1<<6) /* Load PTP Trigger */
+#define PTP_RD_CLK (1<<5) /* Read PTP Clock */
+#define PTP_LOAD_CLK (1<<4) /* Load PTP Clock */
+#define PTP_STEP_CLK (1<<3) /* Step PTP Clock */
+#define PTP_ENABLE (1<<2) /* Enable PTP Clock */
+#define PTP_DISABLE (1<<1) /* Disable PTP Clock */
+#define PTP_RESET (1<<0) /* Reset PTP Clock */
+
+/* Bit definitions for the PTP_STS register */
+#define TXTS_RDY (1<<11) /* Transmit Timestamp Ready */
+#define RXTS_RDY (1<<10) /* Receive Timestamp Ready */
+#define TRIG_DONE (1<<9) /* PTP Trigger Done */
+#define EVENT_RDY (1<<8) /* PTP Event Timestamp Ready */
+#define TXTS_IE (1<<3) /* Transmit Timestamp Interrupt Enable */
+#define RXTS_IE (1<<2) /* Receive Timestamp Interrupt Enable */
+#define TRIG_IE (1<<1) /* Trigger Interrupt Enable */
+#define EVENT_IE (1<<0) /* Event Interrupt Enable */
+
+/* Bit definitions for the PTP_TSTS register */
+#define TRIG7_ERROR (1<<15) /* Trigger 7 Error */
+#define TRIG7_ACTIVE (1<<14) /* Trigger 7 Active */
+#define TRIG6_ERROR (1<<13) /* Trigger 6 Error */
+#define TRIG6_ACTIVE (1<<12) /* Trigger 6 Active */
+#define TRIG5_ERROR (1<<11) /* Trigger 5 Error */
+#define TRIG5_ACTIVE (1<<10) /* Trigger 5 Active */
+#define TRIG4_ERROR (1<<9) /* Trigger 4 Error */
+#define TRIG4_ACTIVE (1<<8) /* Trigger 4 Active */
+#define TRIG3_ERROR (1<<7) /* Trigger 3 Error */
+#define TRIG3_ACTIVE (1<<6) /* Trigger 3 Active */
+#define TRIG2_ERROR (1<<5) /* Trigger 2 Error */
+#define TRIG2_ACTIVE (1<<4) /* Trigger 2 Active */
+#define TRIG1_ERROR (1<<3) /* Trigger 1 Error */
+#define TRIG1_ACTIVE (1<<2) /* Trigger 1 Active */
+#define TRIG0_ERROR (1<<1) /* Trigger 0 Error */
+#define TRIG0_ACTIVE (1<<0) /* Trigger 0 Active */
+
+/* Bit definitions for the PTP_RATEH register */
+#define PTP_RATE_DIR (1<<15) /* PTP Rate Direction */
+#define PTP_TMP_RATE (1<<14) /* PTP Temporary Rate */
+#define PTP_RATE_HI_SHIFT (0) /* PTP Rate High 10-bits */
+#define PTP_RATE_HI_MASK (0x3ff)
+
+/* Bit definitions for the PTP_ESTS register */
+#define EVNTS_MISSED_SHIFT (8) /* Indicates number of events missed */
+#define EVNTS_MISSED_MASK (0x7)
+#define EVNT_TS_LEN_SHIFT (6) /* Indicates length of the Timestamp field in 16-bit words minus 1 */
+#define EVNT_TS_LEN_MASK (0x3)
+#define EVNT_RF (1<<5) /* Indicates whether the event is a rise or falling event */
+#define EVNT_NUM_SHIFT (2) /* Indicates Event Timestamp Unit which detected an event */
+#define EVNT_NUM_MASK (0x7)
+#define MULT_EVNT (1<<1) /* Indicates multiple events were detected at the same time */
+#define EVENT_DET (1<<0) /* PTP Event Detected */
+
+/* Bit definitions for the PTP_EDATA register */
+#define E7_RISE (1<<15) /* Indicates direction of Event 7 */
+#define E7_DET (1<<14) /* Indicates Event 7 detected */
+#define E6_RISE (1<<13) /* Indicates direction of Event 6 */
+#define E6_DET (1<<12) /* Indicates Event 6 detected */
+#define E5_RISE (1<<11) /* Indicates direction of Event 5 */
+#define E5_DET (1<<10) /* Indicates Event 5 detected */
+#define E4_RISE (1<<9) /* Indicates direction of Event 4 */
+#define E4_DET (1<<8) /* Indicates Event 4 detected */
+#define E3_RISE (1<<7) /* Indicates direction of Event 3 */
+#define E3_DET (1<<6) /* Indicates Event 3 detected */
+#define E2_RISE (1<<5) /* Indicates direction of Event 2 */
+#define E2_DET (1<<4) /* Indicates Event 2 detected */
+#define E1_RISE (1<<3) /* Indicates direction of Event 1 */
+#define E1_DET (1<<2) /* Indicates Event 1 detected */
+#define E0_RISE (1<<1) /* Indicates direction of Event 0 */
+#define E0_DET (1<<0) /* Indicates Event 0 detected */
+
+/* Bit definitions for the PTP_TRIG register */
+#define TRIG_PULSE (1<<15) /* generate a Pulse rather than a single edge */
+#define TRIG_PER (1<<14) /* generate a periodic signal */
+#define TRIG_IF_LATE (1<<13) /* trigger immediately if already past */
+#define TRIG_NOTIFY (1<<12) /* Trigger Notification Enable */
+#define TRIG_GPIO_SHIFT (8) /* Trigger GPIO Connection, value 1-12 */
+#define TRIG_GPIO_MASK (0xf)
+#define TRIG_TOGGLE (1<<7) /* Trigger Toggle Mode Enable */
+#define TRIG_CSEL_SHIFT (1) /* Trigger Configuration Select */
+#define TRIG_CSEL_MASK (0x7)
+#define TRIG_WR (1<<0) /* Trigger Configuration Write */
+
+/* Bit definitions for the PTP_EVNT register */
+#define EVNT_RISE (1<<14) /* Event Rise Detect Enable */
+#define EVNT_FALL (1<<13) /* Event Fall Detect Enable */
+#define EVNT_SINGLE (1<<12) /* enable single event capture operation */
+#define EVNT_GPIO_SHIFT (8) /* Event GPIO Connection, value 1-12 */
+#define EVNT_GPIO_MASK (0xf)
+#define EVNT_SEL_SHIFT (1) /* Event Select */
+#define EVNT_SEL_MASK (0x7)
+#define EVNT_WR (1<<0) /* Event Configuration Write */
+
+/* Bit definitions for the PTP_TXCFG0 register */
+#define SYNC_1STEP (1<<15) /* insert timestamp into transmit Sync Messages */
+#define DR_INSERT (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */
+#define NTP_TS_EN (1<<12) /* Enable Timestamping of NTP Packets */
+#define IGNORE_2STEP (1<<11) /* Ignore Two_Step flag for One-Step operation */
+#define CRC_1STEP (1<<10) /* Disable checking of CRC for One-Step operation */
+#define CHK_1STEP (1<<9) /* Enable UDP Checksum correction for One-Step Operation */
+#define IP1588_EN (1<<8) /* Enable IEEE 1588 defined IP address filter */
+#define TX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define TX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define TX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define TX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define TX_PTP_VER_MASK (0xf)
+#define TX_TS_EN (1<<0) /* Transmit Timestamp Enable */
+
+/* Bit definitions for the PTP_TXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG0 register */
+#define MAC_SRC_ADD_SHIFT (11) /* Status Frame Mac Source Address */
+#define MAC_SRC_ADD_MASK (0x3)
+#define MIN_PRE_SHIFT (8) /* Status Frame Minimum Preamble */
+#define MIN_PRE_MASK (0x7)
+#define PSF_ENDIAN (1<<7) /* Status Frame Endian Control */
+#define PSF_IPV4 (1<<6) /* Status Frame IPv4 Enable */
+#define PSF_PCF_RD (1<<5) /* Control Frame Read PHY Status Frame Enable */
+#define PSF_ERR_EN (1<<4) /* Error PHY Status Frame Enable */
+#define PSF_TXTS_EN (1<<3) /* Transmit Timestamp PHY Status Frame Enable */
+#define PSF_RXTS_EN (1<<2) /* Receive Timestamp PHY Status Frame Enable */
+#define PSF_TRIG_EN (1<<1) /* Trigger PHY Status Frame Enable */
+#define PSF_EVNT_EN (1<<0) /* Event PHY Status Frame Enable */
+
+/* Bit definitions for the PTP_RXCFG0 register */
+#define DOMAIN_EN (1<<15) /* Domain Match Enable */
+#define ALT_MAST_DIS (1<<14) /* Alternate Master Timestamp Disable */
+#define USER_IP_SEL (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */
+#define USER_IP_EN (1<<12) /* Enable User-programmed IP address filter */
+#define RX_SLAVE (1<<11) /* Receive Slave Only */
+#define IP1588_EN_SHIFT (8) /* Enable IEEE 1588 defined IP address filters */
+#define IP1588_EN_MASK (0xf)
+#define RX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define RX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define RX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define RX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define RX_PTP_VER_MASK (0xf)
+#define RX_TS_EN (1<<0) /* Receive Timestamp Enable */
+
+/* Bit definitions for the PTP_RXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG3 register */
+#define TS_MIN_IFG_SHIFT (12) /* Minimum Inter-frame Gap */
+#define TS_MIN_IFG_MASK (0xf)
+#define ACC_UDP (1<<11) /* Record Timestamp if UDP Checksum Error */
+#define ACC_CRC (1<<10) /* Record Timestamp if CRC Error */
+#define TS_APPEND (1<<9) /* Append Timestamp for L2 */
+#define TS_INSERT (1<<8) /* Enable Timestamp Insertion */
+#define PTP_DOMAIN_SHIFT (0) /* PTP Message domainNumber field */
+#define PTP_DOMAIN_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG4 register */
+#define IPV4_UDP_MOD (1<<15) /* Enable IPV4 UDP Modification */
+#define TS_SEC_EN (1<<14) /* Enable Timestamp Seconds */
+#define TS_SEC_LEN_SHIFT (12) /* Inserted Timestamp Seconds Length */
+#define TS_SEC_LEN_MASK (0x3)
+#define RXTS_NS_OFF_SHIFT (6) /* Receive Timestamp Nanoseconds offset */
+#define RXTS_NS_OFF_MASK (0x3f)
+#define RXTS_SEC_OFF_SHIFT (0) /* Receive Timestamp Seconds offset */
+#define RXTS_SEC_OFF_MASK (0x3f)
+
+/* Bit definitions for the PTP_COC register */
+#define PTP_CLKOUT_EN (1<<15) /* PTP Clock Output Enable */
+#define PTP_CLKOUT_SEL (1<<14) /* PTP Clock Output Source Select */
+#define PTP_CLKOUT_SPEEDSEL (1<<13) /* PTP Clock Output I/O Speed Select */
+#define PTP_CLKDIV_SHIFT (0) /* PTP Clock Divide-by Value */
+#define PTP_CLKDIV_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG1 register */
+#define PTPRESERVED_SHIFT (12) /* PTP v2 reserved field */
+#define PTPRESERVED_MASK (0xf)
+#define VERSIONPTP_SHIFT (8) /* PTP v2 versionPTP field */
+#define VERSIONPTP_MASK (0xf)
+#define TRANSPORT_SPECIFIC_SHIFT (4) /* PTP v2 Header transportSpecific field */
+#define TRANSPORT_SPECIFIC_MASK (0xf)
+#define MESSAGETYPE_SHIFT (0) /* PTP v2 messageType field */
+#define MESSAGETYPE_MASK (0xf)
+
+/* Bit definitions for the PTP_SFDCFG register */
+#define TX_SFD_GPIO_SHIFT (4) /* TX SFD GPIO Select, value 1-12 */
+#define TX_SFD_GPIO_MASK (0xf)
+#define RX_SFD_GPIO_SHIFT (0) /* RX SFD GPIO Select, value 1-12 */
+#define RX_SFD_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_INTCTL register */
+#define PTP_INT_GPIO_SHIFT (0) /* PTP Interrupt GPIO Select */
+#define PTP_INT_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_CLKSRC register */
+#define CLK_SRC_SHIFT (14) /* PTP Clock Source Select */
+#define CLK_SRC_MASK (0x3)
+#define CLK_SRC_PER_SHIFT (0) /* PTP Clock Source Period */
+#define CLK_SRC_PER_MASK (0x7f)
+
+/* Bit definitions for the PTP_OFF register */
+#define PTP_OFFSET_SHIFT (0) /* PTP Message offset from preceding header */
+#define PTP_OFFSET_MASK (0xff)
+
+#endif
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread* [PATCH V8 13/13] ptp: Added a clock driver for the National Semiconductor PHYTER.
@ 2010-12-31 19:17 ` Richard Cochran
0 siblings, 0 replies; 76+ messages in thread
From: Richard Cochran @ 2010-12-31 19:17 UTC (permalink / raw)
To: linux-kernel
Cc: linux-api, netdev, Alan Cox, Arnd Bergmann, Christoph Lameter,
David Miller, John Stultz, Krzysztof Halasa, Peter Zijlstra,
Rodolfo Giometti, Thomas Gleixner
This patch adds support for the PTP clock found on the DP83640.
The basic clock operations and one external time stamp have
been implemented.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
drivers/net/phy/Kconfig | 29 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dp83640.c | 896 +++++++++++++++++++++++++++++++++++++++++
drivers/net/phy/dp83640_reg.h | 261 ++++++++++++
4 files changed, 1187 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/phy/dp83640.c
create mode 100644 drivers/net/phy/dp83640_reg.h
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 35fda5a..1f1399a 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -76,6 +76,35 @@ config NATIONAL_PHY
---help---
Currently supports the DP83865 PHY.
+config DP83640_PHY
+ tristate "Driver for the National Semiconductor DP83640 PHYTER"
+ depends on PTP_1588_CLOCK
+ depends on NETWORK_PHY_TIMESTAMPING
+ ---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 skb_tx_timetamp() function.
+
+config DP83640_PHY_STATUS_FRAMES
+ bool "DP83640 Status Frames"
+ default y
+ depends on DP83640_PHY
+ ---help---
+ This option allows the DP83640 PHYTER driver to obtain time
+ stamps from the PHY via special status frames, rather than
+ reading over the MDIO bus. Using status frames is therefore
+ more efficient. However, if enabled, this option will cause
+ the driver to add a mutlicast address to the MAC.
+
+ Say Y here, unless your MAC does not support multicast
+ destination addresses.
+
config STE10XP
depends on PHYLIB
tristate "Driver for STMicroelectronics STe10Xp PHYs"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 13bebab..2333215 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_FIXED_PHY) += fixed.o
obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
obj-$(CONFIG_NATIONAL_PHY) += national.o
+obj-$(CONFIG_DP83640_PHY) += dp83640.o
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_MICREL_PHY) += micrel.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
new file mode 100644
index 0000000..5860d66
--- /dev/null
+++ b/drivers/net/phy/dp83640.c
@@ -0,0 +1,896 @@
+/*
+ * Driver for the National Semiconductor DP83640 PHYTER
+ *
+ * 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/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+
+#include "dp83640_reg.h"
+
+#ifdef CONFIG_DP83640_PHY_STATUS_FRAMES
+#define USE_STATUS_FRAMES
+#endif
+
+#define DP83640_PHY_ID 0x20005ce1
+#define PAGESEL 0x13
+#define LAYER4 0x02
+#define LAYER2 0x01
+#define MAX_RXTS 4
+#define MAX_TXTS 4
+#define N_EXT_TS 1
+#define PSF_PTPVER 2
+#define PSF_EVNT 0x4000
+#define PSF_RX 0x2000
+#define PSF_TX 0x1000
+#define EXT_EVENT 1
+#define EXT_GPIO 1
+
+#if defined(__BIG_ENDIAN)
+#define ENDIAN_FLAG 0
+#elif defined(__LITTLE_ENDIAN)
+#define ENDIAN_FLAG PSF_ENDIAN
+#endif
+
+#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
+
+struct phy_rxts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+ u16 seqid; /* sequenceId[15:0] */
+ u16 msgtype; /* messageType[3:0], hash[11:0] */
+};
+
+struct phy_txts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+};
+
+struct rxts {
+ struct list_head list;
+ unsigned long tmo;
+ u64 ns;
+ u16 seqid;
+ u8 msgtype;
+ u16 hash;
+};
+
+struct dp83640_private {
+ struct ptp_clock_info caps;
+ struct phy_device *phydev;
+ struct work_struct ts_work;
+ int hwts_tx_en;
+ int hwts_rx_en;
+ int layer;
+ int version;
+ /* protects extended registers from concurrent access */
+ struct mutex extreg_mux;
+ int page;
+ /* remember the last event time stamp */
+ struct phy_txts edata;
+ /* list of rx timestamps */
+ struct list_head rxts;
+ struct list_head rxpool;
+ struct rxts rx_pool_data[MAX_RXTS];
+ /* protects above three fields from concurrent access */
+ spinlock_t rx_lock;
+
+ struct sk_buff_head rx_queue;
+ struct sk_buff_head tx_queue;
+};
+
+/* globals */
+
+static struct ptp_clock *dp83640_clock;
+DEFINE_MUTEX(clock_lock); /* protects the one and only dp83640_clock */
+
+static void do_timestamp_work(struct work_struct *work);
+
+/* extended register access functions */
+
+static int ext_read(struct phy_device *phydev, int page, u32 regnum)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ int val;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ val = phy_read(phydev, regnum);
+
+ return val;
+}
+
+static void ext_write(struct phy_device *phydev, int page, u32 regnum, u16 val)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ phy_write(phydev, regnum, val);
+}
+
+static int tdr_write(struct phy_device *phydev, const struct timespec *ts, u16 cmd)
+{
+ 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);
+
+ return 0;
+}
+
+/* convert phy timestamps into driver timestamps */
+
+static void phy2rxts(struct phy_rxts *p, struct rxts *rxts)
+{
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ rxts->ns = p->ns_lo;
+ rxts->ns |= (p->ns_hi & 0x3fff) << 16;
+ rxts->ns += ((u64)sec) * 1000000000ULL;
+ rxts->seqid = p->seqid;
+ rxts->msgtype = (p->msgtype >> 12) & 0xf;
+ rxts->hash = p->msgtype & 0x0fff;
+}
+
+static u64 phy2txts(struct phy_txts *p)
+{
+ u64 ns;
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ ns = p->ns_lo;
+ ns |= (p->ns_hi & 0x3fff) << 16;
+ ns += ((u64)sec) * 1000000000ULL;
+
+ return ns;
+}
+
+/* ptp clock methods */
+
+static int ptp_dp83640_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct phy_device *phydev = dp83640->phydev;
+ u64 rate;
+ int neg_adj = 0;
+ u16 hi, lo;
+
+ 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;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ ext_write(phydev, PAGE4, PTP_RATEH, hi);
+ ext_write(phydev, PAGE4, PTP_RATEL, lo);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return 0;
+}
+
+static int ptp_dp83640_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct timespec ts;
+ int err;
+
+ ts = ns_to_timespec(delta);
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, &ts, PTP_STEP_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct phy_device *phydev = dp83640->phydev;
+ unsigned int val[4];
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ 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] */
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ ts->tv_nsec = val[0] | (val[1] << 16);
+ ts->tv_sec = val[2] | (val[3] << 16);
+
+ return 0;
+}
+
+static int ptp_dp83640_settime(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ int err;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, ts, PTP_LOAD_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct dp83640_private *dp83640 =
+ container_of(ptp, struct dp83640_private, caps);
+ struct phy_device *phydev = dp83640->phydev;
+ u16 evnt;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ if (rq->extts.index != 0)
+ return -EINVAL;
+ evnt = EVNT_WR | (EXT_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+ if (on) {
+ evnt |= (EXT_GPIO & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+ evnt |= EVNT_RISE;
+ }
+ ext_write(phydev, PAGE5, PTP_EVNT, evnt);
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info dp83640_caps = {
+ .owner = THIS_MODULE,
+ .name = "dp83640 timer",
+ .max_adj = 1953124,
+ .n_alarm = 0,
+ .n_ext_ts = N_EXT_TS,
+ .n_per_out = 0,
+ .pps = 0,
+ .adjfreq = ptp_dp83640_adjfreq,
+ .adjtime = ptp_dp83640_adjtime,
+ .gettime = ptp_dp83640_gettime,
+ .settime = ptp_dp83640_settime,
+ .enable = ptp_dp83640_enable,
+};
+
+/* status frame conditional code */
+
+#ifdef USE_STATUS_FRAMES
+
+static u8 status_frame_dst[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 };
+static u8 status_frame_src[6] = { 0x08, 0x00, 0x17, 0x0B, 0x6B, 0x0F };
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+ u16 cfg0 = 0, ver;
+
+ if (on)
+ cfg0 = PSF_EVNT_EN | PSF_RXTS_EN | PSF_TXTS_EN | ENDIAN_FLAG;
+
+ ver = (PSF_PTPVER & VERSIONPTP_MASK) << VERSIONPTP_SHIFT;
+
+ ext_write(phydev, PAGE5, PSF_CFG0, cfg0);
+ ext_write(phydev, PAGE6, PSF_CFG1, ver);
+
+ if (!phydev->attached_dev) {
+ pr_err("expected to find an attached netdevice\n");
+ BUG();
+ }
+
+ if (on) {
+ if (dev_mc_add(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to add mc address\n");
+ } else {
+ if (dev_mc_del(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to delete mc address\n");
+ }
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ struct ethhdr *h = eth_hdr(skb);
+
+ if (PTP_CLASS_V2_L2 == type &&
+ !memcmp(h->h_source, status_frame_src, sizeof(status_frame_src)))
+ return true;
+ else
+ return false;
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+}
+
+#else /* USE_STATUS_FRAMES */
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ return false;
+}
+
+static void read_rxts(struct phy_device *phydev, struct rxts *rxts)
+{
+ struct phy_rxts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.seqid = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.msgtype = ext_read(phydev, PAGE4, PTP_RXTS);
+
+ rxts->tmo = jiffies + HZ;
+ phy2rxts(&p, rxts);
+}
+
+static u64 read_txts(struct phy_device *phydev)
+{
+ struct phy_txts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+
+ return phy2txts(&p);
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+ struct rxts *rxts;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ for (;;) {
+ int val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & RXTS_RDY))
+ break;
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ break;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ read_rxts(dp83640->phydev, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+ int val;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ while ((skb = skb_dequeue(&dp83640->tx_queue)) != NULL) {
+
+ val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & TXTS_RDY)) {
+ skb_queue_head(&dp83640->tx_queue, skb);
+ break;
+ }
+ ns = read_txts(dp83640->phydev);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+#endif /* !USE_STATUS_FRAMES */
+
+/* time stamping methods */
+
+static void decode_evnt(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts, u16 ests)
+{
+ struct ptp_clock_event event;
+ int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK;
+
+ switch (words) { /* fall through in every case */
+ case 3:
+ dp83640->edata.sec_hi = phy_txts->sec_hi;
+ case 2:
+ dp83640->edata.sec_lo = phy_txts->sec_lo;
+ case 1:
+ dp83640->edata.ns_hi = phy_txts->ns_hi;
+ case 0:
+ dp83640->edata.ns_lo = phy_txts->ns_lo;
+ }
+
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = phy2txts(&dp83640->edata);
+
+ ptp_clock_event(dp83640_clock, &event);
+}
+
+static void decode_rxts(struct dp83640_private *dp83640,
+ struct phy_rxts *phy_rxts)
+{
+ struct rxts *rxts;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ goto out;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ phy2rxts(phy_rxts, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+out:
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void decode_txts(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+
+ /* We must already have the skb that triggered this. */
+
+ skb = skb_dequeue(&dp83640->tx_queue);
+
+ if (!skb) {
+ pr_warning("dp83640: have timestamp but tx_queue empty\n");
+ return;
+ }
+ ns = phy2txts(phy_txts);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+}
+
+static void decode_status_frame(struct dp83640_private *dp83640,
+ struct sk_buff *skb)
+{
+ struct phy_rxts *phy_rxts;
+ struct phy_txts *phy_txts;
+ u8 *ptr;
+ int len, size;
+ u16 ests, type;
+
+ ptr = skb->data + 2;
+
+ for (len = skb_headlen(skb) - 2; len > sizeof(type); len -= size) {
+
+ type = *(u16 *)ptr;
+ ests = type & 0x0fff;
+ type = type & 0xf000;
+ len -= sizeof(type);
+ ptr += sizeof(type);
+
+ if (PSF_RX == type && len >= sizeof(*phy_rxts)) {
+
+ phy_rxts = (struct phy_rxts *) ptr;
+ decode_rxts(dp83640, phy_rxts);
+ size = sizeof(*phy_rxts);
+
+ } else if (PSF_TX == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_txts(dp83640, phy_txts);
+ size = sizeof(*phy_txts);
+
+ } else if (PSF_EVNT == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_evnt(dp83640, phy_txts, ests);
+ size = sizeof(*phy_txts);
+
+ } else {
+ size = 0;
+ break;
+ }
+ ptr += size;
+ }
+}
+
+static int expired(struct rxts *rxts)
+{
+ return time_after(jiffies, rxts->tmo);
+}
+
+static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
+{
+ u16 *seqid;
+ u8 *msgtype, *data = skb_mac_header(skb);
+
+ /* check sequenceID, messageType, 12 bit hash of offset 20-29 */
+ /* We assume that the IPv4 header has no options. */
+
+ switch (type) {
+ case PTP_CLASS_V1_IPV4:
+ msgtype = data + 42 + 32;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V1_IPV6:
+ msgtype = data + 62 + 32;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_IPV4:
+ msgtype = data + 42 + 0;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V2_IPV6:
+ msgtype = data + 62 + 0;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_L2:
+ msgtype = data + 14 + 0;
+ seqid = (u16 *)(data + 14 + 30);
+ break;
+ case PTP_CLASS_V2_VLAN:
+ msgtype = data + 18 + 0;
+ seqid = (u16 *)(data + 18 + 30);
+ break;
+ default:
+ return 0;
+ }
+
+ return ((*msgtype & 0xf) == rxts->msgtype && *seqid == rxts->seqid);
+}
+
+static int dp83640_probe(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640;
+ int i;
+
+ dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
+ if (!dp83640)
+ return -ENOMEM;
+
+ dp83640->phydev = phydev;
+ INIT_WORK(&dp83640->ts_work, do_timestamp_work);
+ mutex_init(&dp83640->extreg_mux);
+
+ INIT_LIST_HEAD(&dp83640->rxts);
+ INIT_LIST_HEAD(&dp83640->rxpool);
+ for (i = 0; i < MAX_RXTS; i++)
+ list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
+
+ phydev->priv = dp83640;
+
+ spin_lock_init(&dp83640->rx_lock);
+ skb_queue_head_init(&dp83640->rx_queue);
+ skb_queue_head_init(&dp83640->tx_queue);
+
+ mutex_lock(&clock_lock);
+
+ if (!dp83640_clock) {
+ dp83640->caps = dp83640_caps;
+ dp83640_clock = ptp_clock_register(&dp83640->caps);
+ if (IS_ERR(dp83640_clock)) {
+ mutex_unlock(&clock_lock);
+ kfree(dp83640);
+ return PTR_ERR(dp83640_clock);
+ }
+ }
+ mutex_unlock(&clock_lock);
+
+ return 0;
+}
+
+static void dp83640_remove(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ enable_status_frames(phydev, false);
+
+ cancel_work_sync(&dp83640->ts_work);
+
+ mutex_lock(&clock_lock);
+
+ if (dp83640->caps.owner) {
+ ptp_clock_unregister(dp83640_clock);
+ dp83640_clock = NULL;
+ }
+ mutex_unlock(&clock_lock);
+
+ mutex_destroy(&dp83640->extreg_lock);
+
+ kfree(dp83640);
+}
+
+static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ struct hwtstamp_config cfg;
+ u16 txcfg0, rxcfg0;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.flags) /* reserved for future extensions */
+ return -EINVAL;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ dp83640->hwts_tx_en = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ dp83640->hwts_tx_en = 1;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ dp83640->hwts_rx_en = 0;
+ dp83640->layer = 0;
+ dp83640->version = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 1;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER2;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4|LAYER2;
+ dp83640->version = 2;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ txcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+ rxcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+
+ if (dp83640->layer & LAYER2) {
+ txcfg0 |= TX_L2_EN;
+ rxcfg0 |= RX_L2_EN;
+ }
+ if (dp83640->layer & LAYER4) {
+ txcfg0 |= TX_IPV6_EN | TX_IPV4_EN;
+ rxcfg0 |= RX_IPV6_EN | RX_IPV4_EN;
+ }
+
+ if (dp83640->hwts_tx_en)
+ txcfg0 |= TX_TS_EN;
+
+ if (dp83640->hwts_rx_en)
+ rxcfg0 |= RX_TS_EN;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ if (dp83640->hwts_tx_en || dp83640->hwts_rx_en) {
+ enable_status_frames(phydev, true);
+ ext_write(phydev, PAGE4, PTP_CTL, PTP_ENABLE);
+ }
+
+ ext_write(phydev, PAGE5, PTP_TXCFG0, txcfg0);
+ ext_write(phydev, PAGE5, PTP_RXCFG0, rxcfg0);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static void rx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct list_head *this, *next;
+ struct rxts *rxts;
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct sk_buff *skb;
+ unsigned int type;
+ unsigned long flags;
+
+ /* Deliver each deferred packet, with or without a time stamp. */
+
+ while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL) {
+ type = SKB_PTP_TYPE(skb);
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (match(skb, type, rxts)) {
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns);
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+ netif_rx(skb);
+ }
+
+ /* Clear out expired time stamps. */
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (expired(rxts)) {
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void do_timestamp_work(struct work_struct *work)
+{
+ struct dp83640_private *dp83640 =
+ container_of(work, struct dp83640_private, ts_work);
+
+ rx_reading_work(dp83640);
+ rx_timestamp_work(dp83640);
+ tx_timestamp_work(dp83640);
+}
+
+static bool dp83640_rxtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_rx_en)
+ return false;
+
+ if (is_status_frame(skb, type)) {
+ decode_status_frame(dp83640, skb);
+ /* Let the stack drop this frame. */
+ return false;
+ }
+
+ SKB_PTP_TYPE(skb) = type;
+ skb_queue_tail(&dp83640->rx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+
+ return true;
+}
+
+static void dp83640_txtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_tx_en) {
+ kfree_skb(skb);
+ return;
+ }
+ skb_queue_tail(&dp83640->tx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+}
+
+static struct phy_driver dp83640_driver = {
+ .phy_id = DP83640_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "NatSemi DP83640",
+ .features = PHY_BASIC_FEATURES,
+ .flags = 0,
+ .probe = dp83640_probe,
+ .remove = dp83640_remove,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .hwtstamp = dp83640_hwtstamp,
+ .rxtstamp = dp83640_rxtstamp,
+ .txtstamp = dp83640_txtstamp,
+ .driver = {.owner = THIS_MODULE,}
+};
+
+static int __init dp83640_init(void)
+{
+ return phy_driver_register(&dp83640_driver);
+}
+
+static void __exit dp83640_exit(void)
+{
+ phy_driver_unregister(&dp83640_driver);
+}
+
+MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver");
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_LICENSE("GPL");
+
+module_init(dp83640_init);
+module_exit(dp83640_exit);
+
+static struct mdio_device_id dp83640_tbl[] = {
+ { DP83640_PHY_ID, 0xfffffff0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, dp83640_tbl);
diff --git a/drivers/net/phy/dp83640_reg.h b/drivers/net/phy/dp83640_reg.h
new file mode 100644
index 0000000..9d34bb6
--- /dev/null
+++ b/drivers/net/phy/dp83640_reg.h
@@ -0,0 +1,261 @@
+/* dp83640_reg.h
+ * Generated by regen.tcl on Fri Jul 23 09:45:18 AM CEST 2010
+ */
+#ifndef HAVE_DP83640_REGISTERS
+#define HAVE_DP83640_REGISTERS
+
+#define PAGE4 0x0004
+#define PTP_CTL 0x0014 /* PTP Control Register */
+#define PTP_TDR 0x0015 /* PTP Time Data Register */
+#define PTP_STS 0x0016 /* PTP Status Register */
+#define PTP_TSTS 0x0017 /* PTP Trigger Status Register */
+#define PTP_RATEL 0x0018 /* PTP Rate Low Register */
+#define PTP_RATEH 0x0019 /* PTP Rate High Register */
+#define PTP_RDCKSUM 0x001a /* PTP Read Checksum */
+#define PTP_WRCKSUM 0x001b /* PTP Write Checksum */
+#define PTP_TXTS 0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */
+#define PTP_RXTS 0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */
+#define PTP_ESTS 0x001e /* PTP Event Status Register */
+#define PTP_EDATA 0x001f /* PTP Event Data Register */
+
+#define PAGE5 0x0005
+#define PTP_TRIG 0x0014 /* PTP Trigger Configuration Register */
+#define PTP_EVNT 0x0015 /* PTP Event Configuration Register */
+#define PTP_TXCFG0 0x0016 /* PTP Transmit Configuration Register 0 */
+#define PTP_TXCFG1 0x0017 /* PTP Transmit Configuration Register 1 */
+#define PSF_CFG0 0x0018 /* PHY Status Frame Configuration Register 0 */
+#define PTP_RXCFG0 0x0019 /* PTP Receive Configuration Register 0 */
+#define PTP_RXCFG1 0x001a /* PTP Receive Configuration Register 1 */
+#define PTP_RXCFG2 0x001b /* PTP Receive Configuration Register 2 */
+#define PTP_RXCFG3 0x001c /* PTP Receive Configuration Register 3 */
+#define PTP_RXCFG4 0x001d /* PTP Receive Configuration Register 4 */
+#define PTP_TRDL 0x001e /* PTP Temporary Rate Duration Low Register */
+#define PTP_TRDH 0x001f /* PTP Temporary Rate Duration High Register */
+
+#define PAGE6 0x0006
+#define PTP_COC 0x0014 /* PTP Clock Output Control Register */
+#define PSF_CFG1 0x0015 /* PHY Status Frame Configuration Register 1 */
+#define PSF_CFG2 0x0016 /* PHY Status Frame Configuration Register 2 */
+#define PSF_CFG3 0x0017 /* PHY Status Frame Configuration Register 3 */
+#define PSF_CFG4 0x0018 /* PHY Status Frame Configuration Register 4 */
+#define PTP_SFDCFG 0x0019 /* PTP SFD Configuration Register */
+#define PTP_INTCTL 0x001a /* PTP Interrupt Control Register */
+#define PTP_CLKSRC 0x001b /* PTP Clock Source Register */
+#define PTP_ETR 0x001c /* PTP Ethernet Type Register */
+#define PTP_OFF 0x001d /* PTP Offset Register */
+#define PTP_GPIOMON 0x001e /* PTP GPIO Monitor Register */
+#define PTP_RXHASH 0x001f /* PTP Receive Hash Register */
+
+/* Bit definitions for the PTP_CTL register */
+#define TRIG_SEL_SHIFT (10) /* PTP Trigger Select */
+#define TRIG_SEL_MASK (0x7)
+#define TRIG_DIS (1<<9) /* Disable PTP Trigger */
+#define TRIG_EN (1<<8) /* Enable PTP Trigger */
+#define TRIG_READ (1<<7) /* Read PTP Trigger */
+#define TRIG_LOAD (1<<6) /* Load PTP Trigger */
+#define PTP_RD_CLK (1<<5) /* Read PTP Clock */
+#define PTP_LOAD_CLK (1<<4) /* Load PTP Clock */
+#define PTP_STEP_CLK (1<<3) /* Step PTP Clock */
+#define PTP_ENABLE (1<<2) /* Enable PTP Clock */
+#define PTP_DISABLE (1<<1) /* Disable PTP Clock */
+#define PTP_RESET (1<<0) /* Reset PTP Clock */
+
+/* Bit definitions for the PTP_STS register */
+#define TXTS_RDY (1<<11) /* Transmit Timestamp Ready */
+#define RXTS_RDY (1<<10) /* Receive Timestamp Ready */
+#define TRIG_DONE (1<<9) /* PTP Trigger Done */
+#define EVENT_RDY (1<<8) /* PTP Event Timestamp Ready */
+#define TXTS_IE (1<<3) /* Transmit Timestamp Interrupt Enable */
+#define RXTS_IE (1<<2) /* Receive Timestamp Interrupt Enable */
+#define TRIG_IE (1<<1) /* Trigger Interrupt Enable */
+#define EVENT_IE (1<<0) /* Event Interrupt Enable */
+
+/* Bit definitions for the PTP_TSTS register */
+#define TRIG7_ERROR (1<<15) /* Trigger 7 Error */
+#define TRIG7_ACTIVE (1<<14) /* Trigger 7 Active */
+#define TRIG6_ERROR (1<<13) /* Trigger 6 Error */
+#define TRIG6_ACTIVE (1<<12) /* Trigger 6 Active */
+#define TRIG5_ERROR (1<<11) /* Trigger 5 Error */
+#define TRIG5_ACTIVE (1<<10) /* Trigger 5 Active */
+#define TRIG4_ERROR (1<<9) /* Trigger 4 Error */
+#define TRIG4_ACTIVE (1<<8) /* Trigger 4 Active */
+#define TRIG3_ERROR (1<<7) /* Trigger 3 Error */
+#define TRIG3_ACTIVE (1<<6) /* Trigger 3 Active */
+#define TRIG2_ERROR (1<<5) /* Trigger 2 Error */
+#define TRIG2_ACTIVE (1<<4) /* Trigger 2 Active */
+#define TRIG1_ERROR (1<<3) /* Trigger 1 Error */
+#define TRIG1_ACTIVE (1<<2) /* Trigger 1 Active */
+#define TRIG0_ERROR (1<<1) /* Trigger 0 Error */
+#define TRIG0_ACTIVE (1<<0) /* Trigger 0 Active */
+
+/* Bit definitions for the PTP_RATEH register */
+#define PTP_RATE_DIR (1<<15) /* PTP Rate Direction */
+#define PTP_TMP_RATE (1<<14) /* PTP Temporary Rate */
+#define PTP_RATE_HI_SHIFT (0) /* PTP Rate High 10-bits */
+#define PTP_RATE_HI_MASK (0x3ff)
+
+/* Bit definitions for the PTP_ESTS register */
+#define EVNTS_MISSED_SHIFT (8) /* Indicates number of events missed */
+#define EVNTS_MISSED_MASK (0x7)
+#define EVNT_TS_LEN_SHIFT (6) /* Indicates length of the Timestamp field in 16-bit words minus 1 */
+#define EVNT_TS_LEN_MASK (0x3)
+#define EVNT_RF (1<<5) /* Indicates whether the event is a rise or falling event */
+#define EVNT_NUM_SHIFT (2) /* Indicates Event Timestamp Unit which detected an event */
+#define EVNT_NUM_MASK (0x7)
+#define MULT_EVNT (1<<1) /* Indicates multiple events were detected at the same time */
+#define EVENT_DET (1<<0) /* PTP Event Detected */
+
+/* Bit definitions for the PTP_EDATA register */
+#define E7_RISE (1<<15) /* Indicates direction of Event 7 */
+#define E7_DET (1<<14) /* Indicates Event 7 detected */
+#define E6_RISE (1<<13) /* Indicates direction of Event 6 */
+#define E6_DET (1<<12) /* Indicates Event 6 detected */
+#define E5_RISE (1<<11) /* Indicates direction of Event 5 */
+#define E5_DET (1<<10) /* Indicates Event 5 detected */
+#define E4_RISE (1<<9) /* Indicates direction of Event 4 */
+#define E4_DET (1<<8) /* Indicates Event 4 detected */
+#define E3_RISE (1<<7) /* Indicates direction of Event 3 */
+#define E3_DET (1<<6) /* Indicates Event 3 detected */
+#define E2_RISE (1<<5) /* Indicates direction of Event 2 */
+#define E2_DET (1<<4) /* Indicates Event 2 detected */
+#define E1_RISE (1<<3) /* Indicates direction of Event 1 */
+#define E1_DET (1<<2) /* Indicates Event 1 detected */
+#define E0_RISE (1<<1) /* Indicates direction of Event 0 */
+#define E0_DET (1<<0) /* Indicates Event 0 detected */
+
+/* Bit definitions for the PTP_TRIG register */
+#define TRIG_PULSE (1<<15) /* generate a Pulse rather than a single edge */
+#define TRIG_PER (1<<14) /* generate a periodic signal */
+#define TRIG_IF_LATE (1<<13) /* trigger immediately if already past */
+#define TRIG_NOTIFY (1<<12) /* Trigger Notification Enable */
+#define TRIG_GPIO_SHIFT (8) /* Trigger GPIO Connection, value 1-12 */
+#define TRIG_GPIO_MASK (0xf)
+#define TRIG_TOGGLE (1<<7) /* Trigger Toggle Mode Enable */
+#define TRIG_CSEL_SHIFT (1) /* Trigger Configuration Select */
+#define TRIG_CSEL_MASK (0x7)
+#define TRIG_WR (1<<0) /* Trigger Configuration Write */
+
+/* Bit definitions for the PTP_EVNT register */
+#define EVNT_RISE (1<<14) /* Event Rise Detect Enable */
+#define EVNT_FALL (1<<13) /* Event Fall Detect Enable */
+#define EVNT_SINGLE (1<<12) /* enable single event capture operation */
+#define EVNT_GPIO_SHIFT (8) /* Event GPIO Connection, value 1-12 */
+#define EVNT_GPIO_MASK (0xf)
+#define EVNT_SEL_SHIFT (1) /* Event Select */
+#define EVNT_SEL_MASK (0x7)
+#define EVNT_WR (1<<0) /* Event Configuration Write */
+
+/* Bit definitions for the PTP_TXCFG0 register */
+#define SYNC_1STEP (1<<15) /* insert timestamp into transmit Sync Messages */
+#define DR_INSERT (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */
+#define NTP_TS_EN (1<<12) /* Enable Timestamping of NTP Packets */
+#define IGNORE_2STEP (1<<11) /* Ignore Two_Step flag for One-Step operation */
+#define CRC_1STEP (1<<10) /* Disable checking of CRC for One-Step operation */
+#define CHK_1STEP (1<<9) /* Enable UDP Checksum correction for One-Step Operation */
+#define IP1588_EN (1<<8) /* Enable IEEE 1588 defined IP address filter */
+#define TX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define TX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define TX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define TX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define TX_PTP_VER_MASK (0xf)
+#define TX_TS_EN (1<<0) /* Transmit Timestamp Enable */
+
+/* Bit definitions for the PTP_TXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG0 register */
+#define MAC_SRC_ADD_SHIFT (11) /* Status Frame Mac Source Address */
+#define MAC_SRC_ADD_MASK (0x3)
+#define MIN_PRE_SHIFT (8) /* Status Frame Minimum Preamble */
+#define MIN_PRE_MASK (0x7)
+#define PSF_ENDIAN (1<<7) /* Status Frame Endian Control */
+#define PSF_IPV4 (1<<6) /* Status Frame IPv4 Enable */
+#define PSF_PCF_RD (1<<5) /* Control Frame Read PHY Status Frame Enable */
+#define PSF_ERR_EN (1<<4) /* Error PHY Status Frame Enable */
+#define PSF_TXTS_EN (1<<3) /* Transmit Timestamp PHY Status Frame Enable */
+#define PSF_RXTS_EN (1<<2) /* Receive Timestamp PHY Status Frame Enable */
+#define PSF_TRIG_EN (1<<1) /* Trigger PHY Status Frame Enable */
+#define PSF_EVNT_EN (1<<0) /* Event PHY Status Frame Enable */
+
+/* Bit definitions for the PTP_RXCFG0 register */
+#define DOMAIN_EN (1<<15) /* Domain Match Enable */
+#define ALT_MAST_DIS (1<<14) /* Alternate Master Timestamp Disable */
+#define USER_IP_SEL (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */
+#define USER_IP_EN (1<<12) /* Enable User-programmed IP address filter */
+#define RX_SLAVE (1<<11) /* Receive Slave Only */
+#define IP1588_EN_SHIFT (8) /* Enable IEEE 1588 defined IP address filters */
+#define IP1588_EN_MASK (0xf)
+#define RX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define RX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define RX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define RX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define RX_PTP_VER_MASK (0xf)
+#define RX_TS_EN (1<<0) /* Receive Timestamp Enable */
+
+/* Bit definitions for the PTP_RXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG3 register */
+#define TS_MIN_IFG_SHIFT (12) /* Minimum Inter-frame Gap */
+#define TS_MIN_IFG_MASK (0xf)
+#define ACC_UDP (1<<11) /* Record Timestamp if UDP Checksum Error */
+#define ACC_CRC (1<<10) /* Record Timestamp if CRC Error */
+#define TS_APPEND (1<<9) /* Append Timestamp for L2 */
+#define TS_INSERT (1<<8) /* Enable Timestamp Insertion */
+#define PTP_DOMAIN_SHIFT (0) /* PTP Message domainNumber field */
+#define PTP_DOMAIN_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG4 register */
+#define IPV4_UDP_MOD (1<<15) /* Enable IPV4 UDP Modification */
+#define TS_SEC_EN (1<<14) /* Enable Timestamp Seconds */
+#define TS_SEC_LEN_SHIFT (12) /* Inserted Timestamp Seconds Length */
+#define TS_SEC_LEN_MASK (0x3)
+#define RXTS_NS_OFF_SHIFT (6) /* Receive Timestamp Nanoseconds offset */
+#define RXTS_NS_OFF_MASK (0x3f)
+#define RXTS_SEC_OFF_SHIFT (0) /* Receive Timestamp Seconds offset */
+#define RXTS_SEC_OFF_MASK (0x3f)
+
+/* Bit definitions for the PTP_COC register */
+#define PTP_CLKOUT_EN (1<<15) /* PTP Clock Output Enable */
+#define PTP_CLKOUT_SEL (1<<14) /* PTP Clock Output Source Select */
+#define PTP_CLKOUT_SPEEDSEL (1<<13) /* PTP Clock Output I/O Speed Select */
+#define PTP_CLKDIV_SHIFT (0) /* PTP Clock Divide-by Value */
+#define PTP_CLKDIV_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG1 register */
+#define PTPRESERVED_SHIFT (12) /* PTP v2 reserved field */
+#define PTPRESERVED_MASK (0xf)
+#define VERSIONPTP_SHIFT (8) /* PTP v2 versionPTP field */
+#define VERSIONPTP_MASK (0xf)
+#define TRANSPORT_SPECIFIC_SHIFT (4) /* PTP v2 Header transportSpecific field */
+#define TRANSPORT_SPECIFIC_MASK (0xf)
+#define MESSAGETYPE_SHIFT (0) /* PTP v2 messageType field */
+#define MESSAGETYPE_MASK (0xf)
+
+/* Bit definitions for the PTP_SFDCFG register */
+#define TX_SFD_GPIO_SHIFT (4) /* TX SFD GPIO Select, value 1-12 */
+#define TX_SFD_GPIO_MASK (0xf)
+#define RX_SFD_GPIO_SHIFT (0) /* RX SFD GPIO Select, value 1-12 */
+#define RX_SFD_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_INTCTL register */
+#define PTP_INT_GPIO_SHIFT (0) /* PTP Interrupt GPIO Select */
+#define PTP_INT_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_CLKSRC register */
+#define CLK_SRC_SHIFT (14) /* PTP Clock Source Select */
+#define CLK_SRC_MASK (0x3)
+#define CLK_SRC_PER_SHIFT (0) /* PTP Clock Source Period */
+#define CLK_SRC_PER_MASK (0x7f)
+
+/* Bit definitions for the PTP_OFF register */
+#define PTP_OFFSET_SHIFT (0) /* PTP Message offset from preceding header */
+#define PTP_OFFSET_MASK (0xff)
+
+#endif
--
1.7.0.4
^ permalink raw reply related [flat|nested] 76+ messages in thread