506 lines
14 KiB
C
506 lines
14 KiB
C
|
/* Copyright (c) 2012, 2014-2015, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/clocksource.h>
|
||
|
#include <linux/clockchips.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/irq.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/cpu.h>
|
||
|
#include <soc/qcom/event_timer.h>
|
||
|
|
||
|
/**
|
||
|
* struct event_timer_info - basic event timer structure
|
||
|
* @node: timerqueue node to track time ordered data structure
|
||
|
* of event timers
|
||
|
* @notify: irq affinity notifier.
|
||
|
* @timer: hrtimer created for this event.
|
||
|
* @function : callback function for event timer.
|
||
|
* @data : callback data for event timer.
|
||
|
* @irq: irq number for which event timer is created.
|
||
|
* @cpu: event timer associated cpu.
|
||
|
*/
|
||
|
struct event_timer_info {
|
||
|
struct timerqueue_node node;
|
||
|
struct irq_affinity_notify notify;
|
||
|
void (*function)(void *);
|
||
|
void *data;
|
||
|
int irq;
|
||
|
int cpu;
|
||
|
};
|
||
|
|
||
|
struct hrtimer_info {
|
||
|
struct hrtimer event_hrtimer;
|
||
|
bool timer_initialized;
|
||
|
};
|
||
|
|
||
|
static DEFINE_PER_CPU(struct hrtimer_info, per_cpu_hrtimer);
|
||
|
|
||
|
static DEFINE_PER_CPU(struct timerqueue_head, timer_head) = {
|
||
|
.head = RB_ROOT,
|
||
|
.next = NULL,
|
||
|
};
|
||
|
|
||
|
static DEFINE_SPINLOCK(event_timer_lock);
|
||
|
static DEFINE_SPINLOCK(event_setup_lock);
|
||
|
|
||
|
static void create_timer_smp(void *data);
|
||
|
static void setup_event_hrtimer(struct event_timer_info *event);
|
||
|
static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer);
|
||
|
static void irq_affinity_change_notifier(struct irq_affinity_notify *notify,
|
||
|
const cpumask_t *new_cpu_mask);
|
||
|
static void irq_affinity_release(struct kref *ref);
|
||
|
|
||
|
static int msm_event_debug_mask;
|
||
|
module_param_named(
|
||
|
debug_mask, msm_event_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
|
||
|
);
|
||
|
|
||
|
enum {
|
||
|
MSM_EVENT_TIMER_DEBUG = 1U << 0,
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* add_event_timer() : Add a wakeup event. Intended to be called
|
||
|
* by clients once. Returns a handle to be used
|
||
|
* for future transactions.
|
||
|
* @irq: event associated irq number.
|
||
|
* @function : The callback function will be called when event
|
||
|
* timer expires.
|
||
|
* @data: callback data provided by client.
|
||
|
*/
|
||
|
struct event_timer_info *add_event_timer(uint32_t irq,
|
||
|
void (*function)(void *), void *data)
|
||
|
{
|
||
|
struct event_timer_info *event_info =
|
||
|
kzalloc(sizeof(struct event_timer_info), GFP_KERNEL);
|
||
|
|
||
|
if (!event_info)
|
||
|
return NULL;
|
||
|
|
||
|
event_info->function = function;
|
||
|
event_info->data = data;
|
||
|
|
||
|
if (irq) {
|
||
|
struct irq_desc *desc = irq_to_desc(irq);
|
||
|
struct cpumask *mask = desc->irq_data.affinity;
|
||
|
|
||
|
get_online_cpus();
|
||
|
event_info->cpu = cpumask_any_and(mask, cpu_online_mask);
|
||
|
if (event_info->cpu >= nr_cpu_ids)
|
||
|
event_info->cpu = cpumask_first(cpu_online_mask);
|
||
|
|
||
|
event_info->notify.notify = irq_affinity_change_notifier;
|
||
|
event_info->notify.release = irq_affinity_release;
|
||
|
irq_set_affinity_notifier(irq, &event_info->notify);
|
||
|
put_online_cpus();
|
||
|
}
|
||
|
|
||
|
/* Init rb node and hr timer */
|
||
|
timerqueue_init(&event_info->node);
|
||
|
pr_debug("New Event Added. Event %p(on cpu%d). irq %d.\n",
|
||
|
event_info, event_info->cpu, irq);
|
||
|
|
||
|
return event_info;
|
||
|
}
|
||
|
EXPORT_SYMBOL(add_event_timer);
|
||
|
|
||
|
/**
|
||
|
* is_event_next(): Helper function to check if the event is the next
|
||
|
* expiring event
|
||
|
* @event : handle to the event to be checked.
|
||
|
*/
|
||
|
static bool is_event_next(struct event_timer_info *event)
|
||
|
{
|
||
|
struct event_timer_info *next_event;
|
||
|
struct timerqueue_node *next;
|
||
|
bool ret = false;
|
||
|
|
||
|
next = timerqueue_getnext(&per_cpu(timer_head, event->cpu));
|
||
|
if (!next)
|
||
|
goto exit_is_next_event;
|
||
|
|
||
|
next_event = container_of(next, struct event_timer_info, node);
|
||
|
if (!next_event)
|
||
|
goto exit_is_next_event;
|
||
|
|
||
|
if (next_event == event)
|
||
|
ret = true;
|
||
|
|
||
|
exit_is_next_event:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* is_event_active(): Helper function to check if the timer for a given event
|
||
|
* has been started.
|
||
|
* @event : handle to the event to be checked.
|
||
|
*/
|
||
|
static bool is_event_active(struct event_timer_info *event)
|
||
|
{
|
||
|
struct timerqueue_node *next;
|
||
|
struct event_timer_info *next_event;
|
||
|
bool ret = false;
|
||
|
|
||
|
for (next = timerqueue_getnext(&per_cpu(timer_head, event->cpu)); next;
|
||
|
next = timerqueue_iterate_next(next)) {
|
||
|
next_event = container_of(next, struct event_timer_info, node);
|
||
|
|
||
|
if (event == next_event) {
|
||
|
ret = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* create_hrtimer(): Helper function to setup hrtimer.
|
||
|
*/
|
||
|
static void create_hrtimer(struct event_timer_info *event)
|
||
|
|
||
|
{
|
||
|
bool timer_initialized = per_cpu(per_cpu_hrtimer.timer_initialized,
|
||
|
event->cpu);
|
||
|
struct hrtimer *event_hrtimer = &per_cpu(per_cpu_hrtimer.event_hrtimer,
|
||
|
event->cpu);
|
||
|
|
||
|
if (!timer_initialized) {
|
||
|
hrtimer_init(event_hrtimer, CLOCK_MONOTONIC,
|
||
|
HRTIMER_MODE_ABS_PINNED);
|
||
|
per_cpu(per_cpu_hrtimer.timer_initialized, event->cpu) = true;
|
||
|
}
|
||
|
|
||
|
event_hrtimer->function = event_hrtimer_cb;
|
||
|
hrtimer_start(event_hrtimer, event->node.expires,
|
||
|
HRTIMER_MODE_ABS_PINNED);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* event_hrtimer_cb() : Callback function for hr timer.
|
||
|
* Make the client CB from here and remove the event
|
||
|
* from the time ordered queue.
|
||
|
*/
|
||
|
static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer)
|
||
|
{
|
||
|
struct event_timer_info *event;
|
||
|
struct timerqueue_node *next;
|
||
|
unsigned long flags;
|
||
|
int cpu;
|
||
|
|
||
|
spin_lock_irqsave(&event_timer_lock, flags);
|
||
|
cpu = smp_processor_id();
|
||
|
next = timerqueue_getnext(&per_cpu(timer_head, cpu));
|
||
|
|
||
|
while (next && (ktime_to_ns(next->expires)
|
||
|
<= ktime_to_ns(hrtimer->node.expires))) {
|
||
|
event = container_of(next, struct event_timer_info, node);
|
||
|
if (!event)
|
||
|
goto hrtimer_cb_exit;
|
||
|
|
||
|
WARN_ON_ONCE(event->cpu != cpu);
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Deleting event %p @ %lu(on cpu%d)\n", event,
|
||
|
(unsigned long)ktime_to_ns(next->expires), cpu);
|
||
|
|
||
|
timerqueue_del(&per_cpu(timer_head, cpu), &event->node);
|
||
|
|
||
|
if (event->function)
|
||
|
event->function(event->data);
|
||
|
|
||
|
next = timerqueue_getnext(&per_cpu(timer_head, cpu));
|
||
|
}
|
||
|
|
||
|
if (next) {
|
||
|
event = container_of(next, struct event_timer_info, node);
|
||
|
create_hrtimer(event);
|
||
|
}
|
||
|
hrtimer_cb_exit:
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
return HRTIMER_NORESTART;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* create_timer_smp(): Helper function used setting up timer on CPUs.
|
||
|
*/
|
||
|
static void create_timer_smp(void *data)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
struct event_timer_info *event =
|
||
|
(struct event_timer_info *)data;
|
||
|
struct timerqueue_node *next;
|
||
|
|
||
|
spin_lock_irqsave(&event_timer_lock, flags);
|
||
|
|
||
|
if (is_event_active(event))
|
||
|
timerqueue_del(&per_cpu(timer_head, event->cpu), &event->node);
|
||
|
|
||
|
next = timerqueue_getnext(&per_cpu(timer_head, event->cpu));
|
||
|
timerqueue_add(&per_cpu(timer_head, event->cpu), &event->node);
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Adding Event %p(on cpu%d) for %lu\n", event,
|
||
|
event->cpu,
|
||
|
(unsigned long)ktime_to_ns(event->node.expires));
|
||
|
|
||
|
if (!next || (next && (ktime_to_ns(event->node.expires) <
|
||
|
ktime_to_ns(next->expires)))) {
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Setting timer for %lu(on cpu%d)\n",
|
||
|
(unsigned long)ktime_to_ns(event->node.expires),
|
||
|
event->cpu);
|
||
|
|
||
|
create_hrtimer(event);
|
||
|
}
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* setup_timer() : Helper function to setup timer on primary
|
||
|
* core during hrtimer callback.
|
||
|
* @event: event handle causing the wakeup.
|
||
|
*/
|
||
|
static void setup_event_hrtimer(struct event_timer_info *event)
|
||
|
{
|
||
|
smp_call_function_single(event->cpu, create_timer_smp, event, 1);
|
||
|
}
|
||
|
|
||
|
static void irq_affinity_release(struct kref *ref)
|
||
|
{
|
||
|
struct event_timer_info *event;
|
||
|
struct irq_affinity_notify *notify =
|
||
|
container_of(ref, struct irq_affinity_notify, kref);
|
||
|
|
||
|
event = container_of(notify, struct event_timer_info, notify);
|
||
|
pr_debug("event = %p\n", event);
|
||
|
}
|
||
|
|
||
|
static void irq_affinity_change_notifier(struct irq_affinity_notify *notify,
|
||
|
const cpumask_t *mask_val)
|
||
|
{
|
||
|
struct event_timer_info *event;
|
||
|
unsigned long flags;
|
||
|
unsigned int irq;
|
||
|
int old_cpu = -EINVAL, new_cpu = -EINVAL;
|
||
|
bool next_event = false;
|
||
|
|
||
|
event = container_of(notify, struct event_timer_info, notify);
|
||
|
irq = notify->irq;
|
||
|
|
||
|
if (!event)
|
||
|
return;
|
||
|
|
||
|
/*
|
||
|
* This logic is inline with irq-gic.c for finding
|
||
|
* the next affinity CPU.
|
||
|
*/
|
||
|
new_cpu = cpumask_any_and(mask_val, cpu_online_mask);
|
||
|
if (new_cpu >= nr_cpu_ids)
|
||
|
return;
|
||
|
|
||
|
old_cpu = event->cpu;
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("irq %d, event %p, old_cpu(%d)->new_cpu(%d).\n",
|
||
|
irq, event, old_cpu, new_cpu);
|
||
|
|
||
|
/* No change in IRQ affinity */
|
||
|
if (old_cpu == new_cpu)
|
||
|
return;
|
||
|
|
||
|
spin_lock_irqsave(&event_timer_lock, flags);
|
||
|
|
||
|
/* If the event is not active OR
|
||
|
* If it is the next event
|
||
|
* and the timer is already in callback
|
||
|
* Just reset cpu and return
|
||
|
*/
|
||
|
if (!is_event_active(event) ||
|
||
|
(is_event_next(event) &&
|
||
|
(hrtimer_try_to_cancel(&per_cpu(per_cpu_hrtimer.
|
||
|
event_hrtimer, old_cpu)) < 0))) {
|
||
|
event->cpu = new_cpu;
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Event:%p is not active or in callback\n",
|
||
|
event);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Update the flag based on EVENT is next are not */
|
||
|
if (is_event_next(event))
|
||
|
next_event = true;
|
||
|
|
||
|
event->cpu = new_cpu;
|
||
|
|
||
|
/*
|
||
|
* We are here either because hrtimer was active or event is not next
|
||
|
* Delete the event from the timer queue anyway
|
||
|
*/
|
||
|
timerqueue_del(&per_cpu(timer_head, old_cpu), &event->node);
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Event:%p is in the list\n", event);
|
||
|
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
|
||
|
/*
|
||
|
* Migrating event timer to a new CPU is automatically
|
||
|
* taken care. Since we have already modify the event->cpu
|
||
|
* with new CPU.
|
||
|
*
|
||
|
* Typical cases are
|
||
|
*
|
||
|
* 1)
|
||
|
* C0 C1
|
||
|
* | ^
|
||
|
* ----------------- |
|
||
|
* | | | |
|
||
|
* E1 E2 E3 |
|
||
|
* |(migrating) |
|
||
|
* -------------------------
|
||
|
*
|
||
|
* 2)
|
||
|
* C0 C1
|
||
|
* | ^
|
||
|
* ---------------- |
|
||
|
* | | | |
|
||
|
* E1 E2 E3 |
|
||
|
* |(migrating) |
|
||
|
* ---------------------------------
|
||
|
*
|
||
|
* Here after moving the E1 to C1. Need to start
|
||
|
* E2 on C0.
|
||
|
*/
|
||
|
spin_lock(&event_setup_lock);
|
||
|
/* Setup event timer on new cpu*/
|
||
|
setup_event_hrtimer(event);
|
||
|
|
||
|
/* Setup event on the old cpu*/
|
||
|
if (next_event) {
|
||
|
struct timerqueue_node *next;
|
||
|
|
||
|
next = timerqueue_getnext(&per_cpu(timer_head, old_cpu));
|
||
|
if (next) {
|
||
|
event = container_of(next,
|
||
|
struct event_timer_info, node);
|
||
|
setup_event_hrtimer(event);
|
||
|
}
|
||
|
}
|
||
|
spin_unlock(&event_setup_lock);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* activate_event_timer() : Set the expiration time for an event in absolute
|
||
|
* ktime. This is a oneshot event timer, clients
|
||
|
* should call this again to set another expiration.
|
||
|
* @event : event handle.
|
||
|
* @event_time : event time in absolute ktime.
|
||
|
*/
|
||
|
void activate_event_timer(struct event_timer_info *event, ktime_t event_time)
|
||
|
{
|
||
|
if (!event)
|
||
|
return;
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Adding event %p timer @ %lu(on cpu%d)\n", event,
|
||
|
(unsigned long)ktime_to_us(event_time),
|
||
|
event->cpu);
|
||
|
|
||
|
spin_lock(&event_setup_lock);
|
||
|
event->node.expires = event_time;
|
||
|
/* Start hrtimer and add event to rb tree */
|
||
|
setup_event_hrtimer(event);
|
||
|
spin_unlock(&event_setup_lock);
|
||
|
}
|
||
|
EXPORT_SYMBOL(activate_event_timer);
|
||
|
|
||
|
/**
|
||
|
* deactivate_event_timer() : Deactivate an event timer, this removes the event from
|
||
|
* the time ordered queue of event timers.
|
||
|
* @event: event handle.
|
||
|
*/
|
||
|
void deactivate_event_timer(struct event_timer_info *event)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Deactivate timer\n");
|
||
|
|
||
|
spin_lock_irqsave(&event_timer_lock, flags);
|
||
|
if (is_event_active(event)) {
|
||
|
if (is_event_next(event))
|
||
|
hrtimer_try_to_cancel(&per_cpu(
|
||
|
per_cpu_hrtimer.event_hrtimer, event->cpu));
|
||
|
|
||
|
timerqueue_del(&per_cpu(timer_head, event->cpu), &event->node);
|
||
|
}
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* destroy_event_timer() : Free the event info data structure allocated during
|
||
|
* add_event_timer().
|
||
|
* @event: event handle.
|
||
|
*/
|
||
|
void destroy_event_timer(struct event_timer_info *event)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&event_timer_lock, flags);
|
||
|
if (is_event_active(event)) {
|
||
|
if (is_event_next(event))
|
||
|
hrtimer_try_to_cancel(&per_cpu(
|
||
|
per_cpu_hrtimer.event_hrtimer, event->cpu));
|
||
|
|
||
|
timerqueue_del(&per_cpu(timer_head, event->cpu), &event->node);
|
||
|
}
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
kfree(event);
|
||
|
}
|
||
|
EXPORT_SYMBOL(destroy_event_timer);
|
||
|
|
||
|
/**
|
||
|
* get_next_event_timer() - Get the next wakeup event. Returns
|
||
|
* a ktime value of the next expiring event.
|
||
|
*/
|
||
|
ktime_t get_next_event_time(int cpu)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
struct timerqueue_node *next;
|
||
|
struct event_timer_info *event;
|
||
|
ktime_t next_event = ns_to_ktime(0);
|
||
|
|
||
|
spin_lock_irqsave(&event_timer_lock, flags);
|
||
|
next = timerqueue_getnext(&per_cpu(timer_head, cpu));
|
||
|
event = container_of(next, struct event_timer_info, node);
|
||
|
spin_unlock_irqrestore(&event_timer_lock, flags);
|
||
|
|
||
|
if (!next || event->cpu != cpu)
|
||
|
return next_event;
|
||
|
|
||
|
next_event = hrtimer_get_remaining(
|
||
|
&per_cpu(per_cpu_hrtimer.event_hrtimer, cpu));
|
||
|
|
||
|
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
|
||
|
pr_debug("Next Event %lu(on cpu%d)\n",
|
||
|
(unsigned long)ktime_to_us(next_event), cpu);
|
||
|
|
||
|
return next_event;
|
||
|
}
|