a PTP hardware clock supporting timers (alarms) can now be used via the POSIX 
timer (timer_create, timer_settime etc) interface

Signed-off-by: Kieran Tyrrell <kie...@sienda.com>
---
 drivers/ptp/ptp_clock.c          | 233 +++++++++++++++++++++++++++++++++++++++
 drivers/ptp/ptp_private.h        |   4 +
 include/linux/ptp_clock_kernel.h |   2 +
 kernel/signal.c                  |   1 +
 4 files changed, 240 insertions(+)

diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index 2e481b9..067e41c 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -36,6 +36,8 @@
 #define PTP_PPS_EVENT PPS_CAPTUREASSERT
 #define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
 
+#define PTP_TIMER_MINIMUM_INTERVAL_NS 100000
+
 /* private globals */
 
 static dev_t ptp_devt;
@@ -43,6 +45,108 @@ static struct class *ptp_class;
 
 static DEFINE_IDA(ptp_clocks_map);
 
+static int ptp_clock_gettime(struct posix_clock *pc, struct timespec *tp);
+
+static int set_device_timer_earliest(struct ptp_clock *ptp)
+{
+       struct timerqueue_node *next;
+       int err;
+       unsigned long tq_lock_flags;
+       struct timespec64 ts;
+
+       spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+       next = timerqueue_getnext(&ptp->timerqueue);
+
+       /* Skip over expired or not set timers */
+       while (next) {
+               if (next->expires.tv64 != 0)
+                       break;
+               next = timerqueue_iterate_next(next);
+       }
+
+       spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+       if (next) {
+               ts = ktime_to_timespec64(next->expires);
+               err = ptp->info->timersettime(ptp->info, &ts);
+               if(err)
+                       return err;
+       }
+
+       return 0;
+}
+
+static void ptp_alarm_work(struct work_struct *work)
+{
+       struct ptp_clock *ptp = container_of(work, struct ptp_clock, 
alarm_work);
+       struct task_struct *task;
+       struct siginfo info;
+       struct timerqueue_node *next;
+       struct k_itimer *kit;
+       struct timespec time_now;
+       s64 ns_now;
+       int shared;
+       bool signal_failed_to_send;
+       unsigned long tq_lock_flags;
+
+       if(0 != ptp_clock_gettime(&ptp->clock, &time_now))
+               return;
+
+       ns_now = timespec_to_ns(&time_now);
+
+       spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+       next = timerqueue_getnext(&ptp->timerqueue);
+
+       /* Skip over expired or not set timers */
+       while (next) {
+               if (next->expires.tv64 != 0)
+                       break;
+               next = timerqueue_iterate_next(next);
+       }
+
+       while (next) {
+               if (next->expires.tv64 > ns_now)
+                       break;
+
+               rcu_read_lock();
+
+               kit = container_of(next, struct k_itimer, it.real.timer.node);
+
+               task = pid_task(kit->it_pid, PIDTYPE_PID);
+               if (task)
+               {
+                       memset(&info, 0, sizeof(info));
+                       info.si_signo = SIGALRM;
+                       info.si_code = SI_TIMER;
+                       info._sifields._timer._tid = kit->it_id;
+                       kit->sigq->info.si_sys_private = 0;
+                       shared = !(kit->it_sigev_notify & SIGEV_THREAD_ID);
+                       signal_failed_to_send = send_sigqueue(kit->sigq, task, 
shared) > 0;
+               }
+               rcu_read_unlock();
+
+               next = timerqueue_iterate_next(next);
+
+               /* update and reinsert the last one that has fired */
+               timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+               if ( (0 == ktime_to_ns(kit->it.real.interval)) || 
signal_failed_to_send) {
+                       /* this is not a periodic timer (or the signal failed 
to send), so stop it */
+                       kit->it.real.timer.node.expires = ns_to_ktime(0);
+               }
+               else {
+                       /* this IS a periodic timer, so set the next fire time 
*/
+                       kit->it.real.timer.node.expires = 
ktime_add(kit->it.real.timer.node.expires, kit->it.real.interval);
+               }
+               timerqueue_add(&ptp->timerqueue, &kit->it.real.timer.node);
+       }
+
+       spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+       set_device_timer_earliest(ptp);
+}
+
 /* time stamp event queue operations */
 
 static inline int queue_free(struct timestamp_event_queue *q)
@@ -163,12 +267,135 @@ static int ptp_clock_adjtime(struct posix_clock *pc, 
struct timex *tx)
        return err;
 }
 
+static int ptp_timer_create(struct posix_clock *pc, struct k_itimer *kit)
+{
+       struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+       int err = 0;
+       unsigned long tq_lock_flags;
+
+       if(ptp->info->timerenable == 0)
+               return -EOPNOTSUPP;
+
+       spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+       if(NULL == timerqueue_getnext(&ptp->timerqueue))
+       {
+               /* list is empty, so hardware timer is disabled, enable it */
+               err = ptp->info->timerenable(ptp->info, true);
+       }
+
+       if(0 == err)
+       {
+               timerqueue_init(&kit->it.real.timer.node);
+               /* ensure expiry time is 0 (timer disabled) */
+               kit->it.real.timer.node.expires = ns_to_ktime(0);
+               timerqueue_add(&ptp->timerqueue, &kit->it.real.timer.node);
+       }
+
+       spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+       return err;
+}
+
+static int ptp_timer_delete(struct posix_clock *pc, struct k_itimer *kit)
+{
+       struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+       int err=0;
+       unsigned long tq_lock_flags;
+
+       if(ptp->info->timerenable == 0)
+               return -EOPNOTSUPP;
+
+       spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+       timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+
+       if(NULL == timerqueue_getnext(&ptp->timerqueue))
+       {
+               /* there are no more timers set on this device, so we can 
disable the hardware timer */
+               err = ptp->info->timerenable(ptp->info, false);
+       }
+
+       spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+       return err;
+}
+
+static void ptp_timer_gettime(struct posix_clock *pc,
+                                       struct k_itimer *kit, struct itimerspec 
*tsp)
+{
+       struct timespec time_now;
+
+       if(NULL == tsp)
+               return;
+
+       if(0 != ptp_clock_gettime(pc, &time_now))
+               return;
+
+       tsp->it_interval = ktime_to_timespec(kit->it.real.interval);
+       tsp->it_value = 
timespec_sub(ktime_to_timespec(kit->it.real.timer.node.expires), time_now);
+}
+
+
+static int ptp_timer_settime(struct posix_clock *pc,
+                                       struct k_itimer *kit, int flags,
+                                       struct itimerspec *tsp, struct 
itimerspec *old)
+{
+       struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+       int err;
+       unsigned long tq_lock_flags;
+       struct timespec time_now;
+       ktime_t fire_time;
+
+       if(ptp->info->timersettime == 0)
+               return -EOPNOTSUPP;
+
+       if (old) {
+               ptp_timer_gettime(pc, kit, old);
+       }
+
+       fire_time = timespec_to_ktime(tsp->it_value);
+
+       if( (fire_time.tv64 != 0) && !(flags & TIMER_ABSTIME))
+       {
+               err = ptp_clock_gettime(pc, &time_now);
+               if(err)
+                       return err;
+               /* convert relative to absolute time */
+               fire_time = ktime_add(fire_time, timespec_to_ktime(time_now));
+       }
+
+       /* remove, update and reinsert the node */
+       spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+       timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+
+       kit->it.real.timer.node.expires = fire_time;
+       kit->it.real.interval = timespec_to_ktime(tsp->it_interval);
+
+#ifdef PTP_TIMER_MINIMUM_INTERVAL_NS
+       if ( (ktime_to_ns(kit->it.real.interval) != 0 )
+                       && 
(ktime_to_ns(kit->it.real.interval)<PTP_TIMER_MINIMUM_INTERVAL_NS) )
+               kit->it.real.interval = 
ns_to_ktime(PTP_TIMER_MINIMUM_INTERVAL_NS);
+#endif
+
+       timerqueue_add(&ptp->timerqueue, &kit->it.real.timer.node);
+
+       spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+       return set_device_timer_earliest(ptp);
+}
+
 static struct posix_clock_operations ptp_clock_ops = {
        .owner          = THIS_MODULE,
        .clock_adjtime  = ptp_clock_adjtime,
        .clock_gettime  = ptp_clock_gettime,
        .clock_getres   = ptp_clock_getres,
        .clock_settime  = ptp_clock_settime,
+       .timer_create = ptp_timer_create,
+       .timer_delete = ptp_timer_delete,
+       .timer_gettime = ptp_timer_gettime,
+       .timer_settime = ptp_timer_settime,
        .ioctl          = ptp_ioctl,
        .open           = ptp_open,
        .poll           = ptp_poll,
@@ -217,6 +444,9 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info 
*info,
        mutex_init(&ptp->tsevq_mux);
        mutex_init(&ptp->pincfg_mux);
        init_waitqueue_head(&ptp->tsev_wq);
+       spin_lock_init(&ptp->tq_lock);
+       timerqueue_init_head(&ptp->timerqueue);
+       INIT_WORK(&ptp->alarm_work, ptp_alarm_work);
 
        /* Create a new device in our class. */
        ptp->dev = device_create(ptp_class, parent, ptp->devid, ptp,
@@ -286,6 +516,8 @@ int ptp_clock_unregister(struct ptp_clock *ptp)
 }
 EXPORT_SYMBOL(ptp_clock_unregister);
 
+
+
 void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
 {
        struct pps_event_time evt;
@@ -293,6 +525,7 @@ void ptp_clock_event(struct ptp_clock *ptp, struct 
ptp_clock_event *event)
        switch (event->type) {
 
        case PTP_CLOCK_ALARM:
+               schedule_work(&ptp->alarm_work);
                break;
 
        case PTP_CLOCK_EXTTS:
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h
index 9c5d414..d491299 100644
--- a/drivers/ptp/ptp_private.h
+++ b/drivers/ptp/ptp_private.h
@@ -54,6 +54,10 @@ struct ptp_clock {
        struct device_attribute *pin_dev_attr;
        struct attribute **pin_attr;
        struct attribute_group pin_attr_group;
+
+       struct timerqueue_head timerqueue;
+       spinlock_t tq_lock;
+       struct work_struct alarm_work;
 };
 
 /*
diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
index 6b15e16..8d953f3 100644
--- a/include/linux/ptp_clock_kernel.h
+++ b/include/linux/ptp_clock_kernel.h
@@ -118,6 +118,8 @@ struct ptp_clock_info {
                      struct ptp_clock_request *request, int on);
        int (*verify)(struct ptp_clock_info *ptp, unsigned int pin,
                      enum ptp_pin_function func, unsigned int chan);
+       int (*timerenable)(struct ptp_clock_info *ptp, bool enable);
+       int (*timersettime)(struct ptp_clock_info *ptp, struct timespec64 *ts);
 };
 
 struct ptp_clock;
diff --git a/kernel/signal.c b/kernel/signal.c
index af21afc..e7331b3 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1561,6 +1561,7 @@ out:
 ret:
        return ret;
 }
+EXPORT_SYMBOL(send_sigqueue);
 
 /*
  * Let a parent know about the death of a child.
-- 
2.1.4



------------------------------------------------------------------------------
_______________________________________________
Linuxptp-devel mailing list
Linuxptp-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxptp-devel

Reply via email to