* Re: [PATCH v8 tip 5/9] tracing: allow BPF programs to call bpf_trace_printk()
From: Alexei Starovoitov @ 2015-03-20 21:44 UTC (permalink / raw)
To: Steven Rostedt
Cc: Ingo Molnar, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <20150320172219.45ff7157@gandalf.local.home>
On 3/20/15 2:22 PM, Steven Rostedt wrote:
>> +/* limited trace_printk()
>> + * only %d %u %x %ld %lu %lx %lld %llu %llx %p conversion specifiers allowed
>> + */
>
> Ah! Again, don't contaminate the rest of the kernel with net comment
> styles! :-)
ok :)
>> + } else if (fmt[i] == 'p') {
>> + mod[fmt_cnt]++;
>> + i++;
>> + if (!isspace(fmt[i]) && fmt[i] != 0)
>
> I wonder if we should allow punctuation here too? None alpha-numeric
> characters?
yes. just checked all of ispunct characters after %p.
All should be fine.
Thanks!
^ permalink raw reply
* Re: [PATCH V7] Allow compaction of unevictable pages
From: Andrew Morton @ 2015-03-20 22:17 UTC (permalink / raw)
To: Eric B Munson
Cc: Vlastimil Babka, Thomas Gleixner, Christoph Lameter,
Peter Zijlstra, Mel Gorman, David Rientjes, Rik van Riel,
Michal Hocko, linux-doc, linux-rt-users, linux-mm, linux-api,
linux-kernel
In-Reply-To: <1426859390-10974-1-git-send-email-emunson@akamai.com>
On Fri, 20 Mar 2015 09:49:50 -0400 Eric B Munson <emunson@akamai.com> wrote:
> Currently, pages which are marked as unevictable are protected from
> compaction, but not from other types of migration. The POSIX real time
> extension explicitly states that mlock() will prevent a major page
> fault, but the spirit of this is that mlock() should give a process the
> ability to control sources of latency, including minor page faults.
> However, the mlock manpage only explicitly says that a locked page will
> not be written to swap and this can cause some confusion. The
> compaction code today does not give a developer who wants to avoid swap
> but wants to have large contiguous areas available any method to achieve
> this state. This patch introduces a sysctl for controlling compaction
> behavior with respect to the unevictable lru. Users that demand no page
> faults after a page is present can set compact_unevictable_allowed to 0
> and users who need the large contiguous areas can enable compaction on
> locked memory by leaving the default value of 1.
Do we really really really need the /proc knob? We're already
migrating these pages so users of mlock will occasionally see some
latency - how likely is it that this patch will significantly damage
anyone?
^ permalink raw reply
* Re: [PATCH V7] Allow compaction of unevictable pages
From: Andrew Morton @ 2015-03-20 22:19 UTC (permalink / raw)
To: Eric B Munson
Cc: Vlastimil Babka, Thomas Gleixner, Christoph Lameter,
Peter Zijlstra, Mel Gorman, David Rientjes, Rik van Riel,
Michal Hocko, linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-rt-users-u79uwXL29TY76Z2rM5mHXA,
linux-mm-Bw31MaZKKs3YtjvyW6yDsg, linux-api-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1426859390-10974-1-git-send-email-emunson-JqFfY2XvxFXQT0dZR+AlfA@public.gmane.org>
On Fri, 20 Mar 2015 09:49:50 -0400 Eric B Munson <emunson-JqFfY2XvxFXQT0dZR+AlfA@public.gmane.org> wrote:
> Documentation/sysctl/vm.txt | 11 +++++++++++
> include/linux/compaction.h | 1 +
> kernel/sysctl.c | 9 +++++++++
> mm/compaction.c | 7 +++++++
Documentation/vm/unevictable-lru.txt might benefit from an update.
^ permalink raw reply
* [PATCH v9 tip 0/9] tracing: attach eBPF programs to kprobes
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
Hi Ingo,
I think it's good to go.
Patch 1 is already in net-next. Patch 3 depends on it.
I'm assuming it's not going to be a problem during merge window.
Patch 3 will have a minor conflict in uapi/linux/bpf.h in linux-next,
since net-next has added new lines to the bpf_prog_type and bpf_func_id enums.
I'm assuming it's not a problem either.
V8->V9:
- fixed comment style and allowed ispunct after %p
- added Steven's Reviewed-by. Thanks Steven!
V7->V8:
- split addition of kprobe flag into separate patch
- switched to __this_cpu_inc in now documented trace_call_bpf()
- converted array into standalone bpf_func_proto and switch statement
(this apporach looks cleanest, especially considering patch 5)
- refactored patch 5 bpf_trace_printk to do strict checking
V6->V7:
- rebase and remove confusing _notrace suffix from preempt_disable/enable
everything else unchanged
V5->V6:
- added simple recursion check to trace_call_bpf()
- added tracex4 example that does kmem_cache_alloc/free tracking.
It remembers every allocated object in a map and user space periodically
prints a set of old objects. With more work in can be made into
simple kmemleak detector.
It was used as a test of recursive kmalloc/kfree: attached to
kprobe/__kmalloc and let program to call kmalloc again.
V4->V5:
- switched to ktime_get_mono_fast_ns() as suggested by Peter
- in libbpf.c fixed zero init of 'union bpf_attr' padding
- fresh rebase on tip/master
V3 discussion:
https://lkml.org/lkml/2015/2/9/738
V3->V4:
- since the boundary of stable ABI in bpf+tracepoints is not clear yet,
I've dropped them for now.
- bpf+syscalls are ok from stable ABI point of view, but bpf+seccomp
would want to do very similar analysis of syscalls, so I've dropped
them as well to take time and define common bpf+syscalls and bpf+seccomp
infra in the future.
- so only bpf+kprobes left. kprobes by definition is not a stable ABI,
so bpf+kprobe is not stable ABI either. To stress on that point added
kernel version attribute that user space must pass along with the program
and kernel will reject programs when version code doesn't match.
So bpf+kprobe is very similar to kernel modules, but unlike modules
version check is not used for safety, but for enforcing 'non-ABI-ness'.
(version check doesn't apply to bpf+sockets which are stable)
Programs are attached to kprobe events via API:
prog_fd = bpf_prog_load(...);
struct perf_event_attr attr = {
.type = PERF_TYPE_TRACEPOINT,
.config = event_id, /* ID of just created kprobe event */
};
event_fd = perf_event_open(&attr,...);
ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
Next step is to prototype TCP stack instrumentation (like web10g) using
bpf+kprobe, but without adding any new code tcp stack.
Though kprobes are slow comparing to tracepoints, they are good enough
for prototyping and trace_marker/debug_tracepoint ideas can accelerate
them in the future.
Alexei Starovoitov (8):
tracing: add kprobe flag
tracing: attach BPF programs to kprobes
tracing: allow BPF programs to call bpf_ktime_get_ns()
tracing: allow BPF programs to call bpf_trace_printk()
samples: bpf: simple non-portable kprobe filter example
samples: bpf: counting example for kfree_skb and write syscall
samples: bpf: IO latency analysis (iosnoop/heatmap)
samples: bpf: kmem_alloc/free tracker
Daniel Borkmann (1):
bpf: make internal bpf API independent of CONFIG_BPF_SYSCALL ifdefs
include/linux/bpf.h | 20 +++-
include/linux/ftrace_event.h | 14 +++
include/uapi/linux/bpf.h | 5 +
include/uapi/linux/perf_event.h | 1 +
kernel/bpf/syscall.c | 7 +-
kernel/events/core.c | 59 +++++++++++
kernel/trace/Makefile | 1 +
kernel/trace/bpf_trace.c | 222 +++++++++++++++++++++++++++++++++++++++
kernel/trace/trace_kprobe.c | 10 +-
samples/bpf/Makefile | 16 +++
samples/bpf/bpf_helpers.h | 6 ++
samples/bpf/bpf_load.c | 125 ++++++++++++++++++++--
samples/bpf/bpf_load.h | 3 +
samples/bpf/libbpf.c | 14 ++-
samples/bpf/libbpf.h | 5 +-
samples/bpf/sock_example.c | 2 +-
samples/bpf/test_verifier.c | 2 +-
samples/bpf/tracex1_kern.c | 50 +++++++++
samples/bpf/tracex1_user.c | 25 +++++
samples/bpf/tracex2_kern.c | 86 +++++++++++++++
samples/bpf/tracex2_user.c | 95 +++++++++++++++++
samples/bpf/tracex3_kern.c | 89 ++++++++++++++++
samples/bpf/tracex3_user.c | 150 ++++++++++++++++++++++++++
samples/bpf/tracex4_kern.c | 54 ++++++++++
samples/bpf/tracex4_user.c | 69 ++++++++++++
25 files changed, 1112 insertions(+), 18 deletions(-)
create mode 100644 kernel/trace/bpf_trace.c
create mode 100644 samples/bpf/tracex1_kern.c
create mode 100644 samples/bpf/tracex1_user.c
create mode 100644 samples/bpf/tracex2_kern.c
create mode 100644 samples/bpf/tracex2_user.c
create mode 100644 samples/bpf/tracex3_kern.c
create mode 100644 samples/bpf/tracex3_user.c
create mode 100644 samples/bpf/tracex4_kern.c
create mode 100644 samples/bpf/tracex4_user.c
--
1.7.9.5
^ permalink raw reply
* [PATCH v9 tip 1/9] bpf: make internal bpf API independent of CONFIG_BPF_SYSCALL ifdefs
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
From: Daniel Borkmann <daniel@iogearbox.net>
Socket filter code and other subsystems with upcoming eBPF support should
not need to deal with the fact that we have CONFIG_BPF_SYSCALL defined or
not.
Having the bpf syscall as a config option is a nice thing and I'd expect
it to stay that way for expert users (I presume one day the default setting
of it might change, though), but code making use of it should not care if
it's actually enabled or not.
Instead, hide this via header files and let the rest deal with it.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
---
include/linux/bpf.h | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index bbfceb756452..c2e21113ecc0 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -113,8 +113,6 @@ struct bpf_prog_type_list {
enum bpf_prog_type type;
};
-void bpf_register_prog_type(struct bpf_prog_type_list *tl);
-
struct bpf_prog;
struct bpf_prog_aux {
@@ -129,11 +127,25 @@ struct bpf_prog_aux {
};
#ifdef CONFIG_BPF_SYSCALL
+void bpf_register_prog_type(struct bpf_prog_type_list *tl);
+
void bpf_prog_put(struct bpf_prog *prog);
+struct bpf_prog *bpf_prog_get(u32 ufd);
#else
-static inline void bpf_prog_put(struct bpf_prog *prog) {}
+static inline void bpf_register_prog_type(struct bpf_prog_type_list *tl)
+{
+}
+
+static inline struct bpf_prog *bpf_prog_get(u32 ufd)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void bpf_prog_put(struct bpf_prog *prog)
+{
+}
#endif
-struct bpf_prog *bpf_prog_get(u32 ufd);
+
/* verify correctness of eBPF program */
int bpf_check(struct bpf_prog *fp, union bpf_attr *attr);
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 2/9] tracing: add kprobe flag
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
add TRACE_EVENT_FL_KPROBE flag to differentiate kprobe type of tracepoints,
since bpf programs can only be attached to kprobe type of
PERF_TYPE_TRACEPOINT perf events.
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
---
include/linux/ftrace_event.h | 3 +++
kernel/trace/trace_kprobe.c | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/linux/ftrace_event.h b/include/linux/ftrace_event.h
index c674ee8f7fca..77325e1a1816 100644
--- a/include/linux/ftrace_event.h
+++ b/include/linux/ftrace_event.h
@@ -252,6 +252,7 @@ enum {
TRACE_EVENT_FL_WAS_ENABLED_BIT,
TRACE_EVENT_FL_USE_CALL_FILTER_BIT,
TRACE_EVENT_FL_TRACEPOINT_BIT,
+ TRACE_EVENT_FL_KPROBE_BIT,
};
/*
@@ -265,6 +266,7 @@ enum {
* it is best to clear the buffers that used it).
* USE_CALL_FILTER - For ftrace internal events, don't use file filter
* TRACEPOINT - Event is a tracepoint
+ * KPROBE - Event is a kprobe
*/
enum {
TRACE_EVENT_FL_FILTERED = (1 << TRACE_EVENT_FL_FILTERED_BIT),
@@ -274,6 +276,7 @@ enum {
TRACE_EVENT_FL_WAS_ENABLED = (1 << TRACE_EVENT_FL_WAS_ENABLED_BIT),
TRACE_EVENT_FL_USE_CALL_FILTER = (1 << TRACE_EVENT_FL_USE_CALL_FILTER_BIT),
TRACE_EVENT_FL_TRACEPOINT = (1 << TRACE_EVENT_FL_TRACEPOINT_BIT),
+ TRACE_EVENT_FL_KPROBE = (1 << TRACE_EVENT_FL_KPROBE_BIT),
};
struct ftrace_event_call {
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index d73f565b4e06..8fa549f6f528 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -1286,7 +1286,7 @@ static int register_kprobe_event(struct trace_kprobe *tk)
kfree(call->print_fmt);
return -ENODEV;
}
- call->flags = 0;
+ call->flags = TRACE_EVENT_FL_KPROBE;
call->class->reg = kprobe_register;
call->data = tk;
ret = trace_add_event_call(call);
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 3/9] tracing: attach BPF programs to kprobes
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
User interface:
struct perf_event_attr attr = {.type = PERF_TYPE_TRACEPOINT, .config = event_id, ...};
event_fd = perf_event_open(&attr,...);
ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
prog_fd is a file descriptor associated with BPF program previously loaded.
event_id is an ID of created kprobe
close(event_fd) - automatically detaches BPF program from it
BPF programs can call in-kernel helper functions to:
- lookup/update/delete elements in maps
- probe_read - wraper of probe_kernel_read() used to access any kernel
data structures
BPF programs receive 'struct pt_regs *' as an input
('struct pt_regs' is architecture dependent)
and return 0 to ignore the event and 1 to store kprobe event into ring buffer.
Note, kprobes are _not_ a stable kernel ABI, so bpf programs attached to
kprobes must be recompiled for every kernel version and user must supply correct
LINUX_VERSION_CODE in attr.kern_version during bpf_prog_load() call.
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
---
include/linux/ftrace_event.h | 11 ++++
include/uapi/linux/bpf.h | 3 +
include/uapi/linux/perf_event.h | 1 +
kernel/bpf/syscall.c | 7 ++-
kernel/events/core.c | 59 ++++++++++++++++++
kernel/trace/Makefile | 1 +
kernel/trace/bpf_trace.c | 130 +++++++++++++++++++++++++++++++++++++++
kernel/trace/trace_kprobe.c | 8 +++
8 files changed, 219 insertions(+), 1 deletion(-)
create mode 100644 kernel/trace/bpf_trace.c
diff --git a/include/linux/ftrace_event.h b/include/linux/ftrace_event.h
index 77325e1a1816..0aa535bc9f05 100644
--- a/include/linux/ftrace_event.h
+++ b/include/linux/ftrace_event.h
@@ -13,6 +13,7 @@ struct trace_array;
struct trace_buffer;
struct tracer;
struct dentry;
+struct bpf_prog;
struct trace_print_flags {
unsigned long mask;
@@ -306,6 +307,7 @@ struct ftrace_event_call {
#ifdef CONFIG_PERF_EVENTS
int perf_refcount;
struct hlist_head __percpu *perf_events;
+ struct bpf_prog *prog;
int (*perf_perm)(struct ftrace_event_call *,
struct perf_event *);
@@ -551,6 +553,15 @@ event_trigger_unlock_commit_regs(struct ftrace_event_file *file,
event_triggers_post_call(file, tt);
}
+#ifdef CONFIG_BPF_SYSCALL
+unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx);
+#else
+static inline unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx)
+{
+ return 1;
+}
+#endif
+
enum {
FILTER_OTHER = 0,
FILTER_STATIC_STRING,
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 45da7ec7d274..b2948feeb70b 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -118,6 +118,7 @@ enum bpf_map_type {
enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC,
BPF_PROG_TYPE_SOCKET_FILTER,
+ BPF_PROG_TYPE_KPROBE,
};
/* flags for BPF_MAP_UPDATE_ELEM command */
@@ -151,6 +152,7 @@ union bpf_attr {
__u32 log_level; /* verbosity level of verifier */
__u32 log_size; /* size of user buffer */
__aligned_u64 log_buf; /* user supplied buffer */
+ __u32 kern_version; /* checked when prog_type=kprobe */
};
} __attribute__((aligned(8)));
@@ -162,6 +164,7 @@ enum bpf_func_id {
BPF_FUNC_map_lookup_elem, /* void *map_lookup_elem(&map, &key) */
BPF_FUNC_map_update_elem, /* int map_update_elem(&map, &key, &value, flags) */
BPF_FUNC_map_delete_elem, /* int map_delete_elem(&map, &key) */
+ BPF_FUNC_probe_read, /* int bpf_probe_read(void *dst, int size, void *src) */
__BPF_FUNC_MAX_ID,
};
diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
index 3c8b45de57ec..ad4dade2a502 100644
--- a/include/uapi/linux/perf_event.h
+++ b/include/uapi/linux/perf_event.h
@@ -382,6 +382,7 @@ struct perf_event_attr {
#define PERF_EVENT_IOC_SET_OUTPUT _IO ('$', 5)
#define PERF_EVENT_IOC_SET_FILTER _IOW('$', 6, char *)
#define PERF_EVENT_IOC_ID _IOR('$', 7, __u64 *)
+#define PERF_EVENT_IOC_SET_BPF _IOW('$', 8, __u32)
enum perf_event_ioc_flags {
PERF_IOC_FLAG_GROUP = 1U << 0,
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 536edc2be307..504c10b990ef 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -16,6 +16,7 @@
#include <linux/file.h>
#include <linux/license.h>
#include <linux/filter.h>
+#include <linux/version.h>
static LIST_HEAD(bpf_map_types);
@@ -467,7 +468,7 @@ struct bpf_prog *bpf_prog_get(u32 ufd)
}
/* last field in 'union bpf_attr' used by this command */
-#define BPF_PROG_LOAD_LAST_FIELD log_buf
+#define BPF_PROG_LOAD_LAST_FIELD kern_version
static int bpf_prog_load(union bpf_attr *attr)
{
@@ -492,6 +493,10 @@ static int bpf_prog_load(union bpf_attr *attr)
if (attr->insn_cnt >= BPF_MAXINSNS)
return -EINVAL;
+ if (type == BPF_PROG_TYPE_KPROBE &&
+ attr->kern_version != LINUX_VERSION_CODE)
+ return -EINVAL;
+
/* plain bpf_prog allocation */
prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);
if (!prog)
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 2709063eb26b..3a45e7f6b2df 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -42,6 +42,8 @@
#include <linux/module.h>
#include <linux/mman.h>
#include <linux/compat.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
#include "internal.h"
@@ -3402,6 +3404,7 @@ errout:
}
static void perf_event_free_filter(struct perf_event *event);
+static void perf_event_free_bpf_prog(struct perf_event *event);
static void free_event_rcu(struct rcu_head *head)
{
@@ -3411,6 +3414,7 @@ static void free_event_rcu(struct rcu_head *head)
if (event->ns)
put_pid_ns(event->ns);
perf_event_free_filter(event);
+ perf_event_free_bpf_prog(event);
kfree(event);
}
@@ -3923,6 +3927,7 @@ static inline int perf_fget_light(int fd, struct fd *p)
static int perf_event_set_output(struct perf_event *event,
struct perf_event *output_event);
static int perf_event_set_filter(struct perf_event *event, void __user *arg);
+static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd);
static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned long arg)
{
@@ -3976,6 +3981,9 @@ static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned lon
case PERF_EVENT_IOC_SET_FILTER:
return perf_event_set_filter(event, (void __user *)arg);
+ case PERF_EVENT_IOC_SET_BPF:
+ return perf_event_set_bpf_prog(event, arg);
+
default:
return -ENOTTY;
}
@@ -6436,6 +6444,49 @@ static void perf_event_free_filter(struct perf_event *event)
ftrace_profile_free_filter(event);
}
+static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
+{
+ struct bpf_prog *prog;
+
+ if (event->attr.type != PERF_TYPE_TRACEPOINT)
+ return -EINVAL;
+
+ if (event->tp_event->prog)
+ return -EEXIST;
+
+ if (!(event->tp_event->flags & TRACE_EVENT_FL_KPROBE))
+ /* bpf programs can only be attached to kprobes */
+ return -EINVAL;
+
+ prog = bpf_prog_get(prog_fd);
+ if (IS_ERR(prog))
+ return PTR_ERR(prog);
+
+ if (prog->aux->prog_type != BPF_PROG_TYPE_KPROBE) {
+ /* valid fd, but invalid bpf program type */
+ bpf_prog_put(prog);
+ return -EINVAL;
+ }
+
+ event->tp_event->prog = prog;
+
+ return 0;
+}
+
+static void perf_event_free_bpf_prog(struct perf_event *event)
+{
+ struct bpf_prog *prog;
+
+ if (!event->tp_event)
+ return;
+
+ prog = event->tp_event->prog;
+ if (prog) {
+ event->tp_event->prog = NULL;
+ bpf_prog_put(prog);
+ }
+}
+
#else
static inline void perf_tp_register(void)
@@ -6451,6 +6502,14 @@ static void perf_event_free_filter(struct perf_event *event)
{
}
+static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
+{
+ return -ENOENT;
+}
+
+static void perf_event_free_bpf_prog(struct perf_event *event)
+{
+}
#endif /* CONFIG_EVENT_TRACING */
#ifdef CONFIG_HAVE_HW_BREAKPOINT
diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
index 98f26588255e..c575a300103b 100644
--- a/kernel/trace/Makefile
+++ b/kernel/trace/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o
endif
obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
+obj-$(CONFIG_BPF_SYSCALL) += bpf_trace.o
obj-$(CONFIG_KPROBE_EVENT) += trace_kprobe.o
obj-$(CONFIG_TRACEPOINTS) += power-traces.o
ifeq ($(CONFIG_PM),y)
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
new file mode 100644
index 000000000000..10ac48da4bb7
--- /dev/null
+++ b/kernel/trace/bpf_trace.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2011-2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/uaccess.h>
+#include "trace.h"
+
+static DEFINE_PER_CPU(int, bpf_prog_active);
+
+/**
+ * trace_call_bpf - invoke BPF program
+ * @prog: BPF program
+ * @ctx: opaque context pointer
+ *
+ * kprobe handlers execute BPF programs via this helper.
+ * Can be used from static tracepoints in the future.
+ *
+ * Return: BPF programs always return an integer which is interpreted by
+ * kprobe handler as:
+ * 0 - return from kprobe (event is filtered out)
+ * 1 - store kprobe event into ring buffer
+ * Other values are reserved and currently alias to 1
+ */
+unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx)
+{
+ unsigned int ret;
+
+ if (in_nmi()) /* not supported yet */
+ return 1;
+
+ preempt_disable();
+
+ if (unlikely(__this_cpu_inc_return(bpf_prog_active) != 1)) {
+ /*
+ * since some bpf program is already running on this cpu,
+ * don't call into another bpf program (same or different)
+ * and don't send kprobe event into ring-buffer,
+ * so return zero here
+ */
+ ret = 0;
+ goto out;
+ }
+
+ rcu_read_lock();
+ ret = BPF_PROG_RUN(prog, ctx);
+ rcu_read_unlock();
+
+ out:
+ __this_cpu_dec(bpf_prog_active);
+ preempt_enable();
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(trace_call_bpf);
+
+static u64 bpf_probe_read(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
+{
+ void *dst = (void *) (long) r1;
+ int size = (int) r2;
+ void *unsafe_ptr = (void *) (long) r3;
+
+ return probe_kernel_read(dst, unsafe_ptr, size);
+}
+
+static const struct bpf_func_proto bpf_probe_read_proto = {
+ .func = bpf_probe_read,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_STACK,
+ .arg2_type = ARG_CONST_STACK_SIZE,
+ .arg3_type = ARG_ANYTHING,
+};
+
+static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func_id)
+{
+ switch (func_id) {
+ case BPF_FUNC_map_lookup_elem:
+ return &bpf_map_lookup_elem_proto;
+ case BPF_FUNC_map_update_elem:
+ return &bpf_map_update_elem_proto;
+ case BPF_FUNC_map_delete_elem:
+ return &bpf_map_delete_elem_proto;
+ case BPF_FUNC_probe_read:
+ return &bpf_probe_read_proto;
+ default:
+ return NULL;
+ }
+}
+
+/* bpf+kprobe programs can access fields of 'struct pt_regs' */
+static bool kprobe_prog_is_valid_access(int off, int size, enum bpf_access_type type)
+{
+ /* check bounds */
+ if (off < 0 || off >= sizeof(struct pt_regs))
+ return false;
+
+ /* only read is allowed */
+ if (type != BPF_READ)
+ return false;
+
+ /* disallow misaligned access */
+ if (off % size != 0)
+ return false;
+
+ return true;
+}
+
+static struct bpf_verifier_ops kprobe_prog_ops = {
+ .get_func_proto = kprobe_prog_func_proto,
+ .is_valid_access = kprobe_prog_is_valid_access,
+};
+
+static struct bpf_prog_type_list kprobe_tl = {
+ .ops = &kprobe_prog_ops,
+ .type = BPF_PROG_TYPE_KPROBE,
+};
+
+static int __init register_kprobe_prog_ops(void)
+{
+ bpf_register_prog_type(&kprobe_tl);
+ return 0;
+}
+late_initcall(register_kprobe_prog_ops);
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index 8fa549f6f528..dc3462507d7c 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -1134,11 +1134,15 @@ static void
kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
{
struct ftrace_event_call *call = &tk->tp.call;
+ struct bpf_prog *prog = call->prog;
struct kprobe_trace_entry_head *entry;
struct hlist_head *head;
int size, __size, dsize;
int rctx;
+ if (prog && !trace_call_bpf(prog, regs))
+ return;
+
head = this_cpu_ptr(call->perf_events);
if (hlist_empty(head))
return;
@@ -1165,11 +1169,15 @@ kretprobe_perf_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
struct pt_regs *regs)
{
struct ftrace_event_call *call = &tk->tp.call;
+ struct bpf_prog *prog = call->prog;
struct kretprobe_trace_entry_head *entry;
struct hlist_head *head;
int size, __size, dsize;
int rctx;
+ if (prog && !trace_call_bpf(prog, regs))
+ return;
+
head = this_cpu_ptr(call->perf_events);
if (hlist_empty(head))
return;
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 4/9] tracing: allow BPF programs to call bpf_ktime_get_ns()
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api-u79uwXL29TY76Z2rM5mHXA,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1426894210-27441-1-git-send-email-ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
bpf_ktime_get_ns() is used by programs to compute time delta between events
or as a timestamp
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
Reviewed-by: Steven Rostedt <rostedt-nx8X9YLhiw1AfugRpC6u6w@public.gmane.org>
---
include/uapi/linux/bpf.h | 1 +
kernel/trace/bpf_trace.c | 14 ++++++++++++++
2 files changed, 15 insertions(+)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index b2948feeb70b..238c6883877b 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -165,6 +165,7 @@ enum bpf_func_id {
BPF_FUNC_map_update_elem, /* int map_update_elem(&map, &key, &value, flags) */
BPF_FUNC_map_delete_elem, /* int map_delete_elem(&map, &key) */
BPF_FUNC_probe_read, /* int bpf_probe_read(void *dst, int size, void *src) */
+ BPF_FUNC_ktime_get_ns, /* u64 bpf_ktime_get_ns(void) */
__BPF_FUNC_MAX_ID,
};
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 10ac48da4bb7..96371fa2aad9 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -78,6 +78,18 @@ static const struct bpf_func_proto bpf_probe_read_proto = {
.arg3_type = ARG_ANYTHING,
};
+static u64 bpf_ktime_get_ns(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
+{
+ /* NMI safe access to clock monotonic */
+ return ktime_get_mono_fast_ns();
+}
+
+static const struct bpf_func_proto bpf_ktime_get_ns_proto = {
+ .func = bpf_ktime_get_ns,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+};
+
static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func_id)
{
switch (func_id) {
@@ -89,6 +101,8 @@ static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func
return &bpf_map_delete_elem_proto;
case BPF_FUNC_probe_read:
return &bpf_probe_read_proto;
+ case BPF_FUNC_ktime_get_ns:
+ return &bpf_ktime_get_ns_proto;
default:
return NULL;
}
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 5/9] tracing: allow BPF programs to call bpf_trace_printk()
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api-u79uwXL29TY76Z2rM5mHXA,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1426894210-27441-1-git-send-email-ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
Debugging of BPF programs needs some form of printk from the program,
so let programs call limited trace_printk() with %d %u %x %p modifiers only.
Similar to kernel modules, during program load verifier checks whether program
is calling bpf_trace_printk() and if so, kernel allocates trace_printk buffers
and emits big 'this is debug only' banner.
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
Reviewed-by: Steven Rostedt <rostedt-nx8X9YLhiw1AfugRpC6u6w@public.gmane.org>
---
include/uapi/linux/bpf.h | 1 +
kernel/trace/bpf_trace.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 238c6883877b..cc47ef41076a 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -166,6 +166,7 @@ enum bpf_func_id {
BPF_FUNC_map_delete_elem, /* int map_delete_elem(&map, &key) */
BPF_FUNC_probe_read, /* int bpf_probe_read(void *dst, int size, void *src) */
BPF_FUNC_ktime_get_ns, /* u64 bpf_ktime_get_ns(void) */
+ BPF_FUNC_trace_printk, /* int bpf_trace_printk(const char *fmt, int fmt_size, ...) */
__BPF_FUNC_MAX_ID,
};
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 96371fa2aad9..60a0741569fe 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -10,6 +10,7 @@
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/uaccess.h>
+#include <linux/ctype.h>
#include "trace.h"
static DEFINE_PER_CPU(int, bpf_prog_active);
@@ -90,6 +91,74 @@ static const struct bpf_func_proto bpf_ktime_get_ns_proto = {
.ret_type = RET_INTEGER,
};
+/*
+ * limited trace_printk()
+ * only %d %u %x %ld %lu %lx %lld %llu %llx %p conversion specifiers allowed
+ */
+static u64 bpf_trace_printk(u64 r1, u64 fmt_size, u64 r3, u64 r4, u64 r5)
+{
+ char *fmt = (char *) (long) r1;
+ int mod[3] = {};
+ int fmt_cnt = 0;
+ int i;
+
+ /*
+ * bpf_check()->check_func_arg()->check_stack_boundary()
+ * guarantees that fmt points to bpf program stack,
+ * fmt_size bytes of it were initialized and fmt_size > 0
+ */
+ if (fmt[--fmt_size] != 0)
+ return -EINVAL;
+
+ /* check format string for allowed specifiers */
+ for (i = 0; i < fmt_size; i++) {
+ if ((!isprint(fmt[i]) && !isspace(fmt[i])) || !isascii(fmt[i]))
+ return -EINVAL;
+
+ if (fmt[i] != '%')
+ continue;
+
+ if (fmt_cnt >= 3)
+ return -EINVAL;
+
+ /* fmt[i] != 0 && fmt[last] == 0, so we can access fmt[i + 1] */
+ i++;
+ if (fmt[i] == 'l') {
+ mod[fmt_cnt]++;
+ i++;
+ } else if (fmt[i] == 'p') {
+ mod[fmt_cnt]++;
+ i++;
+ if (!isspace(fmt[i]) && !ispunct(fmt[i]) && fmt[i] != 0)
+ return -EINVAL;
+ fmt_cnt++;
+ continue;
+ }
+
+ if (fmt[i] == 'l') {
+ mod[fmt_cnt]++;
+ i++;
+ }
+
+ if (fmt[i] != 'd' && fmt[i] != 'u' && fmt[i] != 'x')
+ return -EINVAL;
+ fmt_cnt++;
+ }
+
+ return __trace_printk(1/* fake ip will not be printed */, fmt,
+ mod[0] == 2 ? r3 : mod[0] == 1 ? (long) r3 : (u32) r3,
+ mod[1] == 2 ? r4 : mod[1] == 1 ? (long) r4 : (u32) r4,
+ mod[2] == 2 ? r5 : mod[2] == 1 ? (long) r5 : (u32) r5);
+}
+
+static const struct bpf_func_proto bpf_trace_printk_proto = {
+ .func = bpf_trace_printk,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_STACK,
+ .arg2_type = ARG_CONST_STACK_SIZE,
+};
+
static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func_id)
{
switch (func_id) {
@@ -103,6 +172,15 @@ static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func
return &bpf_probe_read_proto;
case BPF_FUNC_ktime_get_ns:
return &bpf_ktime_get_ns_proto;
+
+ case BPF_FUNC_trace_printk:
+ /*
+ * this program might be calling bpf_trace_printk,
+ * so allocate per-cpu printk buffers
+ */
+ trace_printk_init_buffers();
+
+ return &bpf_trace_printk_proto;
default:
return NULL;
}
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 6/9] samples: bpf: simple non-portable kprobe filter example
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
tracex1_kern.c - C program compiled into BPF.
It attaches to kprobe:netif_receive_skb
When skb->dev->name == "lo", it prints sample debug message into trace_pipe
via bpf_trace_printk() helper function.
tracex1_user.c - corresponding user space component that:
- loads bpf program via bpf() syscall
- opens kprobes:netif_receive_skb event via perf_event_open() syscall
- attaches the program to event via ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
- prints from trace_pipe
Note, this bpf program is completely non-portable. It must be recompiled
with current kernel headers. kprobe is not a stable ABI and bpf+kprobe scripts
may stop working any time.
bpf verifier will detect that it's using bpf_trace_printk() and kernel will
print warning banner:
** trace_printk() being used. Allocating extra memory. **
** **
** This means that this is a DEBUG kernel and it is **
** unsafe for production use. **
bpf_trace_printk() should be used for debugging of bpf program only.
Usage:
$ sudo tracex1
ping-19826 [000] d.s2 63103.382648: : skb ffff880466b1ca00 len 84
ping-19826 [000] d.s2 63103.382684: : skb ffff880466b1d300 len 84
ping-19826 [000] d.s2 63104.382533: : skb ffff880466b1ca00 len 84
ping-19826 [000] d.s2 63104.382594: : skb ffff880466b1d300 len 84
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
---
samples/bpf/Makefile | 4 ++
samples/bpf/bpf_helpers.h | 6 +++
samples/bpf/bpf_load.c | 125 ++++++++++++++++++++++++++++++++++++++++---
samples/bpf/bpf_load.h | 3 ++
samples/bpf/libbpf.c | 14 ++++-
samples/bpf/libbpf.h | 5 +-
samples/bpf/sock_example.c | 2 +-
samples/bpf/test_verifier.c | 2 +-
samples/bpf/tracex1_kern.c | 50 +++++++++++++++++
samples/bpf/tracex1_user.c | 25 +++++++++
10 files changed, 224 insertions(+), 12 deletions(-)
create mode 100644 samples/bpf/tracex1_kern.c
create mode 100644 samples/bpf/tracex1_user.c
diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile
index b5b3600dcdf5..51f6f01e5a3a 100644
--- a/samples/bpf/Makefile
+++ b/samples/bpf/Makefile
@@ -6,23 +6,27 @@ hostprogs-y := test_verifier test_maps
hostprogs-y += sock_example
hostprogs-y += sockex1
hostprogs-y += sockex2
+hostprogs-y += tracex1
test_verifier-objs := test_verifier.o libbpf.o
test_maps-objs := test_maps.o libbpf.o
sock_example-objs := sock_example.o libbpf.o
sockex1-objs := bpf_load.o libbpf.o sockex1_user.o
sockex2-objs := bpf_load.o libbpf.o sockex2_user.o
+tracex1-objs := bpf_load.o libbpf.o tracex1_user.o
# Tell kbuild to always build the programs
always := $(hostprogs-y)
always += sockex1_kern.o
always += sockex2_kern.o
+always += tracex1_kern.o
HOSTCFLAGS += -I$(objtree)/usr/include
HOSTCFLAGS_bpf_load.o += -I$(objtree)/usr/include -Wno-unused-variable
HOSTLOADLIBES_sockex1 += -lelf
HOSTLOADLIBES_sockex2 += -lelf
+HOSTLOADLIBES_tracex1 += -lelf
# point this to your LLVM backend with bpf support
LLC=$(srctree)/tools/bpf/llvm/bld/Debug+Asserts/bin/llc
diff --git a/samples/bpf/bpf_helpers.h b/samples/bpf/bpf_helpers.h
index ca0333146006..1c872bcf5a80 100644
--- a/samples/bpf/bpf_helpers.h
+++ b/samples/bpf/bpf_helpers.h
@@ -15,6 +15,12 @@ static int (*bpf_map_update_elem)(void *map, void *key, void *value,
(void *) BPF_FUNC_map_update_elem;
static int (*bpf_map_delete_elem)(void *map, void *key) =
(void *) BPF_FUNC_map_delete_elem;
+static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) =
+ (void *) BPF_FUNC_probe_read;
+static unsigned long long (*bpf_ktime_get_ns)(void) =
+ (void *) BPF_FUNC_ktime_get_ns;
+static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) =
+ (void *) BPF_FUNC_trace_printk;
/* llvm builtin functions that eBPF C program may use to
* emit BPF_LD_ABS and BPF_LD_IND instructions
diff --git a/samples/bpf/bpf_load.c b/samples/bpf/bpf_load.c
index 1831d236382b..95c106e4bcdb 100644
--- a/samples/bpf/bpf_load.c
+++ b/samples/bpf/bpf_load.c
@@ -8,29 +8,70 @@
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
+#include <stdlib.h>
#include <linux/bpf.h>
#include <linux/filter.h>
+#include <linux/perf_event.h>
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
#include "libbpf.h"
#include "bpf_helpers.h"
#include "bpf_load.h"
+#define DEBUGFS "/sys/kernel/debug/tracing/"
+
static char license[128];
+static int kern_version;
static bool processed_sec[128];
int map_fd[MAX_MAPS];
int prog_fd[MAX_PROGS];
+int event_fd[MAX_PROGS];
int prog_cnt;
static int load_and_attach(const char *event, struct bpf_insn *prog, int size)
{
- int fd;
bool is_socket = strncmp(event, "socket", 6) == 0;
-
- if (!is_socket)
- /* tracing events tbd */
+ bool is_kprobe = strncmp(event, "kprobe/", 7) == 0;
+ bool is_kretprobe = strncmp(event, "kretprobe/", 10) == 0;
+ enum bpf_prog_type prog_type;
+ char buf[256];
+ int fd, efd, err, id;
+ struct perf_event_attr attr = {};
+
+ attr.type = PERF_TYPE_TRACEPOINT;
+ attr.sample_type = PERF_SAMPLE_RAW;
+ attr.sample_period = 1;
+ attr.wakeup_events = 1;
+
+ if (is_socket) {
+ prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
+ } else if (is_kprobe || is_kretprobe) {
+ prog_type = BPF_PROG_TYPE_KPROBE;
+ } else {
+ printf("Unknown event '%s'\n", event);
return -1;
+ }
+
+ if (is_kprobe || is_kretprobe) {
+ if (is_kprobe)
+ event += 7;
+ else
+ event += 10;
+
+ snprintf(buf, sizeof(buf),
+ "echo '%c:%s %s' >> /sys/kernel/debug/tracing/kprobe_events",
+ is_kprobe ? 'p' : 'r', event, event);
+ err = system(buf);
+ if (err < 0) {
+ printf("failed to create kprobe '%s' error '%s'\n",
+ event, strerror(errno));
+ return -1;
+ }
+ }
- fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
- prog, size, license);
+ fd = bpf_prog_load(prog_type, prog, size, license, kern_version);
if (fd < 0) {
printf("bpf_prog_load() err=%d\n%s", errno, bpf_log_buf);
@@ -39,6 +80,41 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size)
prog_fd[prog_cnt++] = fd;
+ if (is_socket)
+ return 0;
+
+ strcpy(buf, DEBUGFS);
+ strcat(buf, "events/kprobes/");
+ strcat(buf, event);
+ strcat(buf, "/id");
+
+ efd = open(buf, O_RDONLY, 0);
+ if (efd < 0) {
+ printf("failed to open event %s\n", event);
+ return -1;
+ }
+
+ err = read(efd, buf, sizeof(buf));
+ if (err < 0 || err >= sizeof(buf)) {
+ printf("read from '%s' failed '%s'\n", event, strerror(errno));
+ return -1;
+ }
+
+ close(efd);
+
+ buf[err] = 0;
+ id = atoi(buf);
+ attr.config = id;
+
+ efd = perf_event_open(&attr, -1/*pid*/, 0/*cpu*/, -1/*group_fd*/, 0);
+ if (efd < 0) {
+ printf("event %d fd %d err %s\n", id, efd, strerror(errno));
+ return -1;
+ }
+ event_fd[prog_cnt - 1] = efd;
+ ioctl(efd, PERF_EVENT_IOC_ENABLE, 0);
+ ioctl(efd, PERF_EVENT_IOC_SET_BPF, fd);
+
return 0;
}
@@ -135,6 +211,9 @@ int load_bpf_file(char *path)
if (gelf_getehdr(elf, &ehdr) != &ehdr)
return 1;
+ /* clear all kprobes */
+ i = system("echo \"\" > /sys/kernel/debug/tracing/kprobe_events");
+
/* scan over all elf sections to get license and map info */
for (i = 1; i < ehdr.e_shnum; i++) {
@@ -149,6 +228,14 @@ int load_bpf_file(char *path)
if (strcmp(shname, "license") == 0) {
processed_sec[i] = true;
memcpy(license, data->d_buf, data->d_size);
+ } else if (strcmp(shname, "version") == 0) {
+ processed_sec[i] = true;
+ if (data->d_size != sizeof(int)) {
+ printf("invalid size of version section %zd\n",
+ data->d_size);
+ return 1;
+ }
+ memcpy(&kern_version, data->d_buf, sizeof(int));
} else if (strcmp(shname, "maps") == 0) {
processed_sec[i] = true;
if (load_maps(data->d_buf, data->d_size))
@@ -178,7 +265,8 @@ int load_bpf_file(char *path)
if (parse_relo_and_apply(data, symbols, &shdr, insns))
continue;
- if (memcmp(shname_prog, "events/", 7) == 0 ||
+ if (memcmp(shname_prog, "kprobe/", 7) == 0 ||
+ memcmp(shname_prog, "kretprobe/", 10) == 0 ||
memcmp(shname_prog, "socket", 6) == 0)
load_and_attach(shname_prog, insns, data_prog->d_size);
}
@@ -193,7 +281,8 @@ int load_bpf_file(char *path)
if (get_sec(elf, i, &ehdr, &shname, &shdr, &data))
continue;
- if (memcmp(shname, "events/", 7) == 0 ||
+ if (memcmp(shname, "kprobe/", 7) == 0 ||
+ memcmp(shname, "kretprobe/", 10) == 0 ||
memcmp(shname, "socket", 6) == 0)
load_and_attach(shname, data->d_buf, data->d_size);
}
@@ -201,3 +290,23 @@ int load_bpf_file(char *path)
close(fd);
return 0;
}
+
+void read_trace_pipe(void)
+{
+ int trace_fd;
+
+ trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
+ if (trace_fd < 0)
+ return;
+
+ while (1) {
+ static char buf[4096];
+ ssize_t sz;
+
+ sz = read(trace_fd, buf, sizeof(buf));
+ if (sz) {
+ buf[sz] = 0;
+ puts(buf);
+ }
+ }
+}
diff --git a/samples/bpf/bpf_load.h b/samples/bpf/bpf_load.h
index 27789a34f5e6..cbd7c2b532b9 100644
--- a/samples/bpf/bpf_load.h
+++ b/samples/bpf/bpf_load.h
@@ -6,6 +6,7 @@
extern int map_fd[MAX_MAPS];
extern int prog_fd[MAX_PROGS];
+extern int event_fd[MAX_PROGS];
/* parses elf file compiled by llvm .c->.o
* . parses 'maps' section and creates maps via BPF syscall
@@ -21,4 +22,6 @@ extern int prog_fd[MAX_PROGS];
*/
int load_bpf_file(char *path);
+void read_trace_pipe(void);
+
#endif
diff --git a/samples/bpf/libbpf.c b/samples/bpf/libbpf.c
index 46d50b7ddf79..7e1efa7e2ed7 100644
--- a/samples/bpf/libbpf.c
+++ b/samples/bpf/libbpf.c
@@ -81,7 +81,7 @@ char bpf_log_buf[LOG_BUF_SIZE];
int bpf_prog_load(enum bpf_prog_type prog_type,
const struct bpf_insn *insns, int prog_len,
- const char *license)
+ const char *license, int kern_version)
{
union bpf_attr attr = {
.prog_type = prog_type,
@@ -93,6 +93,11 @@ int bpf_prog_load(enum bpf_prog_type prog_type,
.log_level = 1,
};
+ /* assign one field outside of struct init to make sure any
+ * padding is zero initialized
+ */
+ attr.kern_version = kern_version;
+
bpf_log_buf[0] = 0;
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
@@ -121,3 +126,10 @@ int open_raw_sock(const char *name)
return sock;
}
+
+int perf_event_open(struct perf_event_attr *attr, int pid, int cpu,
+ int group_fd, unsigned long flags)
+{
+ return syscall(__NR_perf_event_open, attr, pid, cpu,
+ group_fd, flags);
+}
diff --git a/samples/bpf/libbpf.h b/samples/bpf/libbpf.h
index 58c5fe1bdba1..ac7b09672b46 100644
--- a/samples/bpf/libbpf.h
+++ b/samples/bpf/libbpf.h
@@ -13,7 +13,7 @@ int bpf_get_next_key(int fd, void *key, void *next_key);
int bpf_prog_load(enum bpf_prog_type prog_type,
const struct bpf_insn *insns, int insn_len,
- const char *license);
+ const char *license, int kern_version);
#define LOG_BUF_SIZE 65536
extern char bpf_log_buf[LOG_BUF_SIZE];
@@ -182,4 +182,7 @@ extern char bpf_log_buf[LOG_BUF_SIZE];
/* create RAW socket and bind to interface 'name' */
int open_raw_sock(const char *name);
+struct perf_event_attr;
+int perf_event_open(struct perf_event_attr *attr, int pid, int cpu,
+ int group_fd, unsigned long flags);
#endif
diff --git a/samples/bpf/sock_example.c b/samples/bpf/sock_example.c
index c8ad0404416f..a0ce251c5390 100644
--- a/samples/bpf/sock_example.c
+++ b/samples/bpf/sock_example.c
@@ -56,7 +56,7 @@ static int test_sock(void)
};
prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, sizeof(prog),
- "GPL");
+ "GPL", 0);
if (prog_fd < 0) {
printf("failed to load prog '%s'\n", strerror(errno));
goto cleanup;
diff --git a/samples/bpf/test_verifier.c b/samples/bpf/test_verifier.c
index b96175e90363..740ce97cda5e 100644
--- a/samples/bpf/test_verifier.c
+++ b/samples/bpf/test_verifier.c
@@ -689,7 +689,7 @@ static int test(void)
prog_fd = bpf_prog_load(BPF_PROG_TYPE_UNSPEC, prog,
prog_len * sizeof(struct bpf_insn),
- "GPL");
+ "GPL", 0);
if (tests[i].result == ACCEPT) {
if (prog_fd < 0) {
diff --git a/samples/bpf/tracex1_kern.c b/samples/bpf/tracex1_kern.c
new file mode 100644
index 000000000000..42176fce4847
--- /dev/null
+++ b/samples/bpf/tracex1_kern.c
@@ -0,0 +1,50 @@
+/* Copyright (c) 2013-2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <uapi/linux/bpf.h>
+#include <linux/version.h>
+#include "bpf_helpers.h"
+
+#define _(P) ({typeof(P) val = 0; bpf_probe_read(&val, sizeof(val), &P); val;})
+
+/* kprobe is NOT a stable ABI
+ * kernel functions can be removed, renamed or completely change semantics.
+ * Number of argumnets and their posistions can change, etc.
+ * This bpf+kprobe example can stop working any time.
+ */
+SEC("kprobe/__netif_receive_skb_core")
+int bpf_prog1(struct pt_regs *ctx)
+{
+ /* attaches to kprobe netif_receive_skb,
+ * looks for packets on loobpack device and prints them
+ */
+ char devname[IFNAMSIZ] = {};
+ struct net_device *dev;
+ struct sk_buff *skb;
+ int len;
+
+ /* non-portable! works for the given kernel only */
+ skb = (struct sk_buff *) ctx->di;
+
+ dev = _(skb->dev);
+
+ len = _(skb->len);
+
+ bpf_probe_read(devname, sizeof(devname), dev->name);
+
+ if (devname[0] == 'l' && devname[1] == 'o') {
+ char fmt[] = "skb %p len %d\n";
+ /* using bpf_trace_printk() for DEBUG ONLY */
+ bpf_trace_printk(fmt, sizeof(fmt), skb, len);
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
+u32 _version SEC("version") = LINUX_VERSION_CODE;
diff --git a/samples/bpf/tracex1_user.c b/samples/bpf/tracex1_user.c
new file mode 100644
index 000000000000..31a48183beea
--- /dev/null
+++ b/samples/bpf/tracex1_user.c
@@ -0,0 +1,25 @@
+#include <stdio.h>
+#include <linux/bpf.h>
+#include <unistd.h>
+#include "libbpf.h"
+#include "bpf_load.h"
+
+int main(int ac, char **argv)
+{
+ FILE *f;
+ char filename[256];
+
+ snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
+
+ if (load_bpf_file(filename)) {
+ printf("%s", bpf_log_buf);
+ return 1;
+ }
+
+ f = popen("taskset 1 ping -c5 localhost", "r");
+ (void) f;
+
+ read_trace_pipe();
+
+ return 0;
+}
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 7/9] samples: bpf: counting example for kfree_skb and write syscall
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
this example has two probes in one C file that attach to different kprove events
and use two different maps.
1st probe is x64 specific equivalent of dropmon. It attaches to kfree_skb,
retrevies 'ip' address of kfree_skb() caller and counts number of packet drops
at that 'ip' address. User space prints 'location - count' map every second.
2nd probe attaches to kprobe:sys_write and computes a histogram of different
write sizes
Usage:
$ sudo tracex2
location 0xffffffff81695995 count 1
location 0xffffffff816d0da9 count 2
location 0xffffffff81695995 count 2
location 0xffffffff816d0da9 count 2
location 0xffffffff81695995 count 3
location 0xffffffff816d0da9 count 2
557145+0 records in
557145+0 records out
285258240 bytes (285 MB) copied, 1.02379 s, 279 MB/s
syscall write() stats
byte_size : count distribution
1 -> 1 : 3 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 2 | |
32 -> 63 : 3 | |
64 -> 127 : 1 | |
128 -> 255 : 1 | |
256 -> 511 : 0 | |
512 -> 1023 : 1118968 |************************************* |
Ctrl-C at any time. Kernel will auto cleanup maps and programs
$ addr2line -ape ./bld_x64/vmlinux 0xffffffff81695995 0xffffffff816d0da9
0xffffffff81695995: ./bld_x64/../net/ipv4/icmp.c:1038
0xffffffff816d0da9: ./bld_x64/../net/unix/af_unix.c:1231
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
---
samples/bpf/Makefile | 4 ++
samples/bpf/tracex2_kern.c | 86 +++++++++++++++++++++++++++++++++++++++
samples/bpf/tracex2_user.c | 95 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 185 insertions(+)
create mode 100644 samples/bpf/tracex2_kern.c
create mode 100644 samples/bpf/tracex2_user.c
diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile
index 51f6f01e5a3a..6dd272143733 100644
--- a/samples/bpf/Makefile
+++ b/samples/bpf/Makefile
@@ -7,6 +7,7 @@ hostprogs-y += sock_example
hostprogs-y += sockex1
hostprogs-y += sockex2
hostprogs-y += tracex1
+hostprogs-y += tracex2
test_verifier-objs := test_verifier.o libbpf.o
test_maps-objs := test_maps.o libbpf.o
@@ -14,12 +15,14 @@ sock_example-objs := sock_example.o libbpf.o
sockex1-objs := bpf_load.o libbpf.o sockex1_user.o
sockex2-objs := bpf_load.o libbpf.o sockex2_user.o
tracex1-objs := bpf_load.o libbpf.o tracex1_user.o
+tracex2-objs := bpf_load.o libbpf.o tracex2_user.o
# Tell kbuild to always build the programs
always := $(hostprogs-y)
always += sockex1_kern.o
always += sockex2_kern.o
always += tracex1_kern.o
+always += tracex2_kern.o
HOSTCFLAGS += -I$(objtree)/usr/include
@@ -27,6 +30,7 @@ HOSTCFLAGS_bpf_load.o += -I$(objtree)/usr/include -Wno-unused-variable
HOSTLOADLIBES_sockex1 += -lelf
HOSTLOADLIBES_sockex2 += -lelf
HOSTLOADLIBES_tracex1 += -lelf
+HOSTLOADLIBES_tracex2 += -lelf
# point this to your LLVM backend with bpf support
LLC=$(srctree)/tools/bpf/llvm/bld/Debug+Asserts/bin/llc
diff --git a/samples/bpf/tracex2_kern.c b/samples/bpf/tracex2_kern.c
new file mode 100644
index 000000000000..334348335795
--- /dev/null
+++ b/samples/bpf/tracex2_kern.c
@@ -0,0 +1,86 @@
+/* Copyright (c) 2013-2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/version.h>
+#include <uapi/linux/bpf.h>
+#include "bpf_helpers.h"
+
+struct bpf_map_def SEC("maps") my_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(long),
+ .value_size = sizeof(long),
+ .max_entries = 1024,
+};
+
+/* kprobe is NOT a stable ABI
+ * This bpf+kprobe example can stop working any time.
+ */
+SEC("kprobe/kfree_skb")
+int bpf_prog2(struct pt_regs *ctx)
+{
+ long loc = 0;
+ long init_val = 1;
+ long *value;
+
+ /* x64 specific: read ip of kfree_skb caller.
+ * non-portable version of __builtin_return_address(0)
+ */
+ bpf_probe_read(&loc, sizeof(loc), (void *)ctx->sp);
+
+ value = bpf_map_lookup_elem(&my_map, &loc);
+ if (value)
+ *value += 1;
+ else
+ bpf_map_update_elem(&my_map, &loc, &init_val, BPF_ANY);
+ return 0;
+}
+
+static unsigned int log2(unsigned int v)
+{
+ unsigned int r;
+ unsigned int shift;
+
+ r = (v > 0xFFFF) << 4; v >>= r;
+ shift = (v > 0xFF) << 3; v >>= shift; r |= shift;
+ shift = (v > 0xF) << 2; v >>= shift; r |= shift;
+ shift = (v > 0x3) << 1; v >>= shift; r |= shift;
+ r |= (v >> 1);
+ return r;
+}
+
+static unsigned int log2l(unsigned long v)
+{
+ unsigned int hi = v >> 32;
+ if (hi)
+ return log2(hi) + 32;
+ else
+ return log2(v);
+}
+
+struct bpf_map_def SEC("maps") my_hist_map = {
+ .type = BPF_MAP_TYPE_ARRAY,
+ .key_size = sizeof(u32),
+ .value_size = sizeof(long),
+ .max_entries = 64,
+};
+
+SEC("kprobe/sys_write")
+int bpf_prog3(struct pt_regs *ctx)
+{
+ long write_size = ctx->dx; /* arg3 */
+ long init_val = 1;
+ long *value;
+ u32 index = log2l(write_size);
+
+ value = bpf_map_lookup_elem(&my_hist_map, &index);
+ if (value)
+ __sync_fetch_and_add(value, 1);
+ return 0;
+}
+char _license[] SEC("license") = "GPL";
+u32 _version SEC("version") = LINUX_VERSION_CODE;
diff --git a/samples/bpf/tracex2_user.c b/samples/bpf/tracex2_user.c
new file mode 100644
index 000000000000..91b8d0896fbb
--- /dev/null
+++ b/samples/bpf/tracex2_user.c
@@ -0,0 +1,95 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <linux/bpf.h>
+#include "libbpf.h"
+#include "bpf_load.h"
+
+#define MAX_INDEX 64
+#define MAX_STARS 38
+
+static void stars(char *str, long val, long max, int width)
+{
+ int i;
+
+ for (i = 0; i < (width * val / max) - 1 && i < width - 1; i++)
+ str[i] = '*';
+ if (val > max)
+ str[i - 1] = '+';
+ str[i] = '\0';
+}
+
+static void print_hist(int fd)
+{
+ int key;
+ long value;
+ long data[MAX_INDEX] = {};
+ char starstr[MAX_STARS];
+ int i;
+ int max_ind = -1;
+ long max_value = 0;
+
+ for (key = 0; key < MAX_INDEX; key++) {
+ bpf_lookup_elem(fd, &key, &value);
+ data[key] = value;
+ if (value && key > max_ind)
+ max_ind = key;
+ if (value > max_value)
+ max_value = value;
+ }
+
+ printf(" syscall write() stats\n");
+ printf(" byte_size : count distribution\n");
+ for (i = 1; i <= max_ind + 1; i++) {
+ stars(starstr, data[i - 1], max_value, MAX_STARS);
+ printf("%8ld -> %-8ld : %-8ld |%-*s|\n",
+ (1l << i) >> 1, (1l << i) - 1, data[i - 1],
+ MAX_STARS, starstr);
+ }
+}
+static void int_exit(int sig)
+{
+ print_hist(map_fd[1]);
+ exit(0);
+}
+
+int main(int ac, char **argv)
+{
+ char filename[256];
+ long key, next_key, value;
+ FILE *f;
+ int i;
+
+ snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
+
+ signal(SIGINT, int_exit);
+
+ /* start 'ping' in the background to have some kfree_skb events */
+ f = popen("ping -c5 localhost", "r");
+ (void) f;
+
+ /* start 'dd' in the background to have plenty of 'write' syscalls */
+ f = popen("dd if=/dev/zero of=/dev/null count=5000000", "r");
+ (void) f;
+
+ if (load_bpf_file(filename)) {
+ printf("%s", bpf_log_buf);
+ return 1;
+ }
+
+ for (i = 0; i < 5; i++) {
+ key = 0;
+ while (bpf_get_next_key(map_fd[0], &key, &next_key) == 0) {
+ bpf_lookup_elem(map_fd[0], &next_key, &value);
+ printf("location 0x%lx count %ld\n", next_key, value);
+ key = next_key;
+ }
+ if (key)
+ printf("\n");
+ sleep(1);
+ }
+ print_hist(map_fd[1]);
+
+ return 0;
+}
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 8/9] samples: bpf: IO latency analysis (iosnoop/heatmap)
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
BPF C program attaches to blk_mq_start_request/blk_update_request kprobe events
to calculate IO latency.
For every completed block IO event it computes the time delta in nsec
and records in a histogram map: map[log10(delta)*10]++
User space reads this histogram map every 2 seconds and prints it as a 'heatmap'
using gray shades of text terminal. Black spaces have many events and white
spaces have very few events. Left most space is the smallest latency, right most
space is the largest latency in the range.
Usage:
$ sudo ./tracex3
and do 'sudo dd if=/dev/sda of=/dev/null' in other terminal.
Observe IO latencies and how different activity (like 'make kernel') affects it.
Similar experiments can be done for network transmit latencies, syscalls, etc
'-t' flag prints the heatmap using normal ascii characters:
$ sudo ./tracex3 -t
heatmap of IO latency
# - many events with this latency
- few events
|1us |10us |100us |1ms |10ms |100ms |1s |10s
*ooo. *O.#. # 221
. *# . # 125
.. .o#*.. # 55
. . . . .#O # 37
.# # 175
.#*. # 37
# # 199
. . *#*. # 55
*#..* # 42
# # 266
...***Oo#*OO**o#* . # 629
# # 271
. .#o* o.*o* # 221
. . o* *#O.. # 50
Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
---
samples/bpf/Makefile | 4 ++
samples/bpf/tracex3_kern.c | 89 ++++++++++++++++++++++++++
samples/bpf/tracex3_user.c | 150 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 243 insertions(+)
create mode 100644 samples/bpf/tracex3_kern.c
create mode 100644 samples/bpf/tracex3_user.c
diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile
index 6dd272143733..dcd850546d52 100644
--- a/samples/bpf/Makefile
+++ b/samples/bpf/Makefile
@@ -8,6 +8,7 @@ hostprogs-y += sockex1
hostprogs-y += sockex2
hostprogs-y += tracex1
hostprogs-y += tracex2
+hostprogs-y += tracex3
test_verifier-objs := test_verifier.o libbpf.o
test_maps-objs := test_maps.o libbpf.o
@@ -16,6 +17,7 @@ sockex1-objs := bpf_load.o libbpf.o sockex1_user.o
sockex2-objs := bpf_load.o libbpf.o sockex2_user.o
tracex1-objs := bpf_load.o libbpf.o tracex1_user.o
tracex2-objs := bpf_load.o libbpf.o tracex2_user.o
+tracex3-objs := bpf_load.o libbpf.o tracex3_user.o
# Tell kbuild to always build the programs
always := $(hostprogs-y)
@@ -23,6 +25,7 @@ always += sockex1_kern.o
always += sockex2_kern.o
always += tracex1_kern.o
always += tracex2_kern.o
+always += tracex3_kern.o
HOSTCFLAGS += -I$(objtree)/usr/include
@@ -31,6 +34,7 @@ HOSTLOADLIBES_sockex1 += -lelf
HOSTLOADLIBES_sockex2 += -lelf
HOSTLOADLIBES_tracex1 += -lelf
HOSTLOADLIBES_tracex2 += -lelf
+HOSTLOADLIBES_tracex3 += -lelf
# point this to your LLVM backend with bpf support
LLC=$(srctree)/tools/bpf/llvm/bld/Debug+Asserts/bin/llc
diff --git a/samples/bpf/tracex3_kern.c b/samples/bpf/tracex3_kern.c
new file mode 100644
index 000000000000..b9d66766f07a
--- /dev/null
+++ b/samples/bpf/tracex3_kern.c
@@ -0,0 +1,89 @@
+/* Copyright (c) 2013-2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/version.h>
+#include <uapi/linux/bpf.h>
+#include "bpf_helpers.h"
+
+struct bpf_map_def SEC("maps") my_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(long),
+ .value_size = sizeof(u64),
+ .max_entries = 4096,
+};
+
+/* kprobe is NOT a stable ABI
+ * This bpf+kprobe example can stop working any time.
+ */
+SEC("kprobe/blk_mq_start_request")
+int bpf_prog1(struct pt_regs *ctx)
+{
+ long rq = ctx->di;
+ u64 val = bpf_ktime_get_ns();
+
+ bpf_map_update_elem(&my_map, &rq, &val, BPF_ANY);
+ return 0;
+}
+
+static unsigned int log2l(unsigned long long n)
+{
+#define S(k) if (n >= (1ull << k)) { i += k; n >>= k; }
+ int i = -(n == 0);
+ S(32); S(16); S(8); S(4); S(2); S(1);
+ return i;
+#undef S
+}
+
+#define SLOTS 100
+
+struct bpf_map_def SEC("maps") lat_map = {
+ .type = BPF_MAP_TYPE_ARRAY,
+ .key_size = sizeof(u32),
+ .value_size = sizeof(u64),
+ .max_entries = SLOTS,
+};
+
+SEC("kprobe/blk_update_request")
+int bpf_prog2(struct pt_regs *ctx)
+{
+ long rq = ctx->di;
+ u64 *value, l, base;
+ u32 index;
+
+ value = bpf_map_lookup_elem(&my_map, &rq);
+ if (!value)
+ return 0;
+
+ u64 cur_time = bpf_ktime_get_ns();
+ u64 delta = cur_time - *value;
+
+ bpf_map_delete_elem(&my_map, &rq);
+
+ /* the lines below are computing index = log10(delta)*10
+ * using integer arithmetic
+ * index = 29 ~ 1 usec
+ * index = 59 ~ 1 msec
+ * index = 89 ~ 1 sec
+ * index = 99 ~ 10sec or more
+ * log10(x)*10 = log2(x)*10/log2(10) = log2(x)*3
+ */
+ l = log2l(delta);
+ base = 1ll << l;
+ index = (l * 64 + (delta - base) * 64 / base) * 3 / 64;
+
+ if (index >= SLOTS)
+ index = SLOTS - 1;
+
+ value = bpf_map_lookup_elem(&lat_map, &index);
+ if (value)
+ __sync_fetch_and_add((long *)value, 1);
+
+ return 0;
+}
+char _license[] SEC("license") = "GPL";
+u32 _version SEC("version") = LINUX_VERSION_CODE;
diff --git a/samples/bpf/tracex3_user.c b/samples/bpf/tracex3_user.c
new file mode 100644
index 000000000000..0aaa933ab938
--- /dev/null
+++ b/samples/bpf/tracex3_user.c
@@ -0,0 +1,150 @@
+/* Copyright (c) 2013-2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <string.h>
+#include <linux/bpf.h>
+#include "libbpf.h"
+#include "bpf_load.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+#define SLOTS 100
+
+static void clear_stats(int fd)
+{
+ __u32 key;
+ __u64 value = 0;
+
+ for (key = 0; key < SLOTS; key++)
+ bpf_update_elem(fd, &key, &value, BPF_ANY);
+}
+
+const char *color[] = {
+ "\033[48;5;255m",
+ "\033[48;5;252m",
+ "\033[48;5;250m",
+ "\033[48;5;248m",
+ "\033[48;5;246m",
+ "\033[48;5;244m",
+ "\033[48;5;242m",
+ "\033[48;5;240m",
+ "\033[48;5;238m",
+ "\033[48;5;236m",
+ "\033[48;5;234m",
+ "\033[48;5;232m",
+};
+const int num_colors = ARRAY_SIZE(color);
+
+const char nocolor[] = "\033[00m";
+
+const char *sym[] = {
+ " ",
+ " ",
+ ".",
+ ".",
+ "*",
+ "*",
+ "o",
+ "o",
+ "O",
+ "O",
+ "#",
+ "#",
+};
+
+bool full_range = false;
+bool text_only = false;
+
+static void print_banner(void)
+{
+ if (full_range)
+ printf("|1ns |10ns |100ns |1us |10us |100us"
+ " |1ms |10ms |100ms |1s |10s\n");
+ else
+ printf("|1us |10us |100us |1ms |10ms "
+ "|100ms |1s |10s\n");
+}
+
+static void print_hist(int fd)
+{
+ __u32 key;
+ __u64 value;
+ __u64 cnt[SLOTS];
+ __u64 max_cnt = 0;
+ __u64 total_events = 0;
+
+ for (key = 0; key < SLOTS; key++) {
+ value = 0;
+ bpf_lookup_elem(fd, &key, &value);
+ cnt[key] = value;
+ total_events += value;
+ if (value > max_cnt)
+ max_cnt = value;
+ }
+ clear_stats(fd);
+ for (key = full_range ? 0 : 29; key < SLOTS; key++) {
+ int c = num_colors * cnt[key] / (max_cnt + 1);
+
+ if (text_only)
+ printf("%s", sym[c]);
+ else
+ printf("%s %s", color[c], nocolor);
+ }
+ printf(" # %lld\n", total_events);
+}
+
+int main(int ac, char **argv)
+{
+ char filename[256];
+ int i;
+
+ snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
+
+ if (load_bpf_file(filename)) {
+ printf("%s", bpf_log_buf);
+ return 1;
+ }
+
+ for (i = 1; i < ac; i++) {
+ if (strcmp(argv[i], "-a") == 0) {
+ full_range = true;
+ } else if (strcmp(argv[i], "-t") == 0) {
+ text_only = true;
+ } else if (strcmp(argv[i], "-h") == 0) {
+ printf("Usage:\n"
+ " -a display wider latency range\n"
+ " -t text only\n");
+ return 1;
+ }
+ }
+
+ printf(" heatmap of IO latency\n");
+ if (text_only)
+ printf(" %s", sym[num_colors - 1]);
+ else
+ printf(" %s %s", color[num_colors - 1], nocolor);
+ printf(" - many events with this latency\n");
+
+ if (text_only)
+ printf(" %s", sym[0]);
+ else
+ printf(" %s %s", color[0], nocolor);
+ printf(" - few events\n");
+
+ for (i = 0; ; i++) {
+ if (i % 20 == 0)
+ print_banner();
+ print_hist(map_fd[1]);
+ sleep(2);
+ }
+
+ return 0;
+}
--
1.7.9.5
^ permalink raw reply related
* [PATCH v9 tip 9/9] samples: bpf: kmem_alloc/free tracker
From: Alexei Starovoitov @ 2015-03-20 23:30 UTC (permalink / raw)
To: Ingo Molnar
Cc: Steven Rostedt, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api-u79uwXL29TY76Z2rM5mHXA,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1426894210-27441-1-git-send-email-ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
One bpf program attaches to kmem_cache_alloc_node() and remembers all allocated
objects in the map.
Another program attaches to kmem_cache_free() and deletes corresponding
object from the map.
User space walks the map every second and prints any objects which are
older than 1 second.
Usage:
$ sudo tracex4
Then start few long living processes. The tracex4 will print:
obj 0xffff880465928000 is 13sec old was allocated at ip ffffffff8105dc32
obj 0xffff88043181c280 is 13sec old was allocated at ip ffffffff8105dc32
obj 0xffff880465848000 is 8sec old was allocated at ip ffffffff8105dc32
obj 0xffff8804338bc280 is 15sec old was allocated at ip ffffffff8105dc32
$ addr2line -fispe vmlinux ffffffff8105dc32
do_fork at fork.c:1665
As soon as processes exit the memory is reclaimed and tracex4 prints nothing.
Similar experiment can be done with __kmalloc/kfree pair.
Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
---
samples/bpf/Makefile | 4 +++
samples/bpf/tracex4_kern.c | 54 ++++++++++++++++++++++++++++++++++
samples/bpf/tracex4_user.c | 69 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 127 insertions(+)
create mode 100644 samples/bpf/tracex4_kern.c
create mode 100644 samples/bpf/tracex4_user.c
diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile
index dcd850546d52..fe98fb226e6e 100644
--- a/samples/bpf/Makefile
+++ b/samples/bpf/Makefile
@@ -9,6 +9,7 @@ hostprogs-y += sockex2
hostprogs-y += tracex1
hostprogs-y += tracex2
hostprogs-y += tracex3
+hostprogs-y += tracex4
test_verifier-objs := test_verifier.o libbpf.o
test_maps-objs := test_maps.o libbpf.o
@@ -18,6 +19,7 @@ sockex2-objs := bpf_load.o libbpf.o sockex2_user.o
tracex1-objs := bpf_load.o libbpf.o tracex1_user.o
tracex2-objs := bpf_load.o libbpf.o tracex2_user.o
tracex3-objs := bpf_load.o libbpf.o tracex3_user.o
+tracex4-objs := bpf_load.o libbpf.o tracex4_user.o
# Tell kbuild to always build the programs
always := $(hostprogs-y)
@@ -26,6 +28,7 @@ always += sockex2_kern.o
always += tracex1_kern.o
always += tracex2_kern.o
always += tracex3_kern.o
+always += tracex4_kern.o
HOSTCFLAGS += -I$(objtree)/usr/include
@@ -35,6 +38,7 @@ HOSTLOADLIBES_sockex2 += -lelf
HOSTLOADLIBES_tracex1 += -lelf
HOSTLOADLIBES_tracex2 += -lelf
HOSTLOADLIBES_tracex3 += -lelf
+HOSTLOADLIBES_tracex4 += -lelf -lrt
# point this to your LLVM backend with bpf support
LLC=$(srctree)/tools/bpf/llvm/bld/Debug+Asserts/bin/llc
diff --git a/samples/bpf/tracex4_kern.c b/samples/bpf/tracex4_kern.c
new file mode 100644
index 000000000000..5754e21caf3b
--- /dev/null
+++ b/samples/bpf/tracex4_kern.c
@@ -0,0 +1,54 @@
+/* Copyright (c) 2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <linux/ptrace.h>
+#include <linux/version.h>
+#include <uapi/linux/bpf.h>
+#include "bpf_helpers.h"
+
+struct pair {
+ u64 val;
+ u64 ip;
+};
+
+struct bpf_map_def SEC("maps") my_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(long),
+ .value_size = sizeof(struct pair),
+ .max_entries = 1000000,
+};
+
+/* kprobe is NOT a stable ABI
+ * This bpf+kprobe example can stop working any time.
+ */
+SEC("kprobe/kmem_cache_free")
+int bpf_prog1(struct pt_regs *ctx)
+{
+ long ptr = ctx->si;
+
+ bpf_map_delete_elem(&my_map, &ptr);
+ return 0;
+}
+
+SEC("kretprobe/kmem_cache_alloc_node")
+int bpf_prog2(struct pt_regs *ctx)
+{
+ long ptr = ctx->ax;
+ long ip = 0;
+
+ /* get ip address of kmem_cache_alloc_node() caller */
+ bpf_probe_read(&ip, sizeof(ip), (void *)(ctx->bp + sizeof(ip)));
+
+ struct pair v = {
+ .val = bpf_ktime_get_ns(),
+ .ip = ip,
+ };
+
+ bpf_map_update_elem(&my_map, &ptr, &v, BPF_ANY);
+ return 0;
+}
+char _license[] SEC("license") = "GPL";
+u32 _version SEC("version") = LINUX_VERSION_CODE;
diff --git a/samples/bpf/tracex4_user.c b/samples/bpf/tracex4_user.c
new file mode 100644
index 000000000000..bc4a3bdea6ed
--- /dev/null
+++ b/samples/bpf/tracex4_user.c
@@ -0,0 +1,69 @@
+/* Copyright (c) 2015 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+#include <linux/bpf.h>
+#include "libbpf.h"
+#include "bpf_load.h"
+
+struct pair {
+ long long val;
+ __u64 ip;
+};
+
+static __u64 time_get_ns(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec * 1000000000ull + ts.tv_nsec;
+}
+
+static void print_old_objects(int fd)
+{
+ long long val = time_get_ns();
+ __u64 key, next_key;
+ struct pair v;
+
+ key = write(1, "\e[1;1H\e[2J", 12); /* clear screen */
+
+ key = -1;
+ while (bpf_get_next_key(map_fd[0], &key, &next_key) == 0) {
+ bpf_lookup_elem(map_fd[0], &next_key, &v);
+ key = next_key;
+ if (val - v.val < 1000000000ll)
+ /* object was allocated more then 1 sec ago */
+ continue;
+ printf("obj 0x%llx is %2lldsec old was allocated at ip %llx\n",
+ next_key, (val - v.val) / 1000000000ll, v.ip);
+ }
+}
+
+int main(int ac, char **argv)
+{
+ char filename[256];
+ int i;
+
+ snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
+
+ if (load_bpf_file(filename)) {
+ printf("%s", bpf_log_buf);
+ return 1;
+ }
+
+ for (i = 0; ; i++) {
+ print_old_objects(map_fd[1]);
+ sleep(1);
+ }
+
+ return 0;
+}
--
1.7.9.5
^ permalink raw reply related
* Re: [PATCH v9 tip 0/9] tracing: attach eBPF programs to kprobes
From: Steven Rostedt @ 2015-03-21 4:08 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Ingo Molnar, Namhyung Kim, Arnaldo Carvalho de Melo, Jiri Olsa,
Masami Hiramatsu, David S. Miller, Daniel Borkmann,
Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-1-git-send-email-ast@plumgrid.com>
On Fri, 20 Mar 2015 16:30:01 -0700
Alexei Starovoitov <ast@plumgrid.com> wrote:
> Hi Ingo,
>
> I think it's good to go.
> Patch 1 is already in net-next. Patch 3 depends on it.
> I'm assuming it's not going to be a problem during merge window.
> Patch 3 will have a minor conflict in uapi/linux/bpf.h in linux-next,
> since net-next has added new lines to the bpf_prog_type and bpf_func_id enums.
> I'm assuming it's not a problem either.
>
> V8->V9:
> - fixed comment style and allowed ispunct after %p
> - added Steven's Reviewed-by. Thanks Steven!
Hi Ingo,
I'm fine with this series, so don't let me hold it up from going into
your tree.
Thanks,
-- Steve
^ permalink raw reply
* Re: [PATCH v2] Add virtio-input driver.
From: Dmitry Torokhov @ 2015-03-21 4:43 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: virtio-dev, mst, open list:ABI/API, open list, virtualization,
David Herrmann
In-Reply-To: <1426855170-10997-1-git-send-email-kraxel@redhat.com>
Hi Gerd,
On Fri, Mar 20, 2015 at 01:39:29PM +0100, Gerd Hoffmann wrote:
> virtio-input is basically evdev-events-over-virtio, so this driver isn't
> much more than reading configuration from config space and forwarding
> incoming events to the linux input layer.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
> MAINTAINERS | 6 +
> drivers/virtio/Kconfig | 10 ++
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio_input.c | 335 ++++++++++++++++++++++++++++++++++++++
> include/uapi/linux/Kbuild | 1 +
> include/uapi/linux/virtio_ids.h | 1 +
> include/uapi/linux/virtio_input.h | 75 +++++++++
> 7 files changed, 429 insertions(+)
> create mode 100644 drivers/virtio/virtio_input.c
> create mode 100644 include/uapi/linux/virtio_input.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0e1abe8..585e6cd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10435,6 +10435,12 @@ S: Maintained
> F: drivers/vhost/
> F: include/uapi/linux/vhost.h
>
> +VIRTIO INPUT DRIVER
> +M: Gerd Hoffmann <kraxel@redhat.com>
> +S: Maintained
> +F: drivers/virtio/virtio_input.c
> +F: include/uapi/linux/virtio_input.h
> +
> VIA RHINE NETWORK DRIVER
> M: Roger Luethi <rl@hellgate.ch>
> S: Maintained
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index b546da5..cab9f3f 100644
> --- a/drivers/virtio/Kconfig
> +++ b/drivers/virtio/Kconfig
> @@ -48,6 +48,16 @@ config VIRTIO_BALLOON
>
> If unsure, say M.
>
> +config VIRTIO_INPUT
> + tristate "Virtio input driver"
> + depends on VIRTIO
> + depends on INPUT
> + ---help---
> + This driver supports virtio input devices such as
> + keyboards, mice and tablets.
> +
> + If unsure, say M.
> +
> config VIRTIO_MMIO
> tristate "Platform bus driver for memory mapped virtio devices"
> depends on HAS_IOMEM
> diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
> index d85565b..41e30e3 100644
> --- a/drivers/virtio/Makefile
> +++ b/drivers/virtio/Makefile
> @@ -4,3 +4,4 @@ obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
> virtio_pci-y := virtio_pci_modern.o virtio_pci_common.o
> virtio_pci-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o
> obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
> +obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
> diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c
> new file mode 100644
> index 0000000..dd3215e
> --- /dev/null
> +++ b/drivers/virtio/virtio_input.c
> @@ -0,0 +1,335 @@
> +#include <linux/module.h>
> +#include <linux/virtio.h>
> +#include <linux/input.h>
> +
> +#include <uapi/linux/virtio_ids.h>
> +#include <uapi/linux/virtio_input.h>
> +
> +struct virtio_input {
> + struct virtio_device *vdev;
> + struct input_dev *idev;
> + char name[64];
> + char serial[64];
> + char phys[64];
> + struct virtqueue *evt, *sts;
> + struct virtio_input_event evts[64];
> + struct mutex lock;
> +};
> +
> +static ssize_t serial_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct input_dev *idev = to_input_dev(dev);
> + struct virtio_input *vi = input_get_drvdata(idev);
> +
> + return sprintf(buf, "%s\n", vi->serial);
> +}
> +static DEVICE_ATTR_RO(serial);
Since serial is uniq and uniq is already exposed in sysfs by input core
please remove this attribute (and the rest of attribute group handling).
> +
> +static struct attribute *dev_attrs[] = {
> + &dev_attr_serial.attr,
> + NULL
> +};
> +
> +static umode_t dev_attrs_are_visible(struct kobject *kobj,
> + struct attribute *a, int n)
> +{
> + struct device *dev = container_of(kobj, struct device, kobj);
> + struct input_dev *idev = to_input_dev(dev);
> + struct virtio_input *vi = input_get_drvdata(idev);
> +
> + if (a == &dev_attr_serial.attr && !strlen(vi->serial))
> + return 0;
> +
> + return a->mode;
> +}
> +
> +static struct attribute_group dev_attr_grp = {
> + .attrs = dev_attrs,
> + .is_visible = dev_attrs_are_visible,
> +};
> +
> +static const struct attribute_group *dev_attr_groups[] = {
> + &dev_attr_grp,
> + NULL
> +};
> +
> +static void virtinput_queue_evtbuf(struct virtio_input *vi,
> + struct virtio_input_event *evtbuf)
> +{
> + struct scatterlist sg[1];
> +
> + sg_init_one(sg, evtbuf, sizeof(*evtbuf));
> + virtqueue_add_inbuf(vi->evt, sg, 1, evtbuf, GFP_ATOMIC);
> +}
> +
> +static void virtinput_recv_events(struct virtqueue *vq)
> +{
> + struct virtio_input *vi = vq->vdev->priv;
> + struct virtio_input_event *event;
> + unsigned int len;
> +
> + mutex_lock(&vi->lock);
> + while ((event = virtqueue_get_buf(vi->evt, &len)) != NULL) {
> + input_event(vi->idev,
> + le16_to_cpu(event->type),
> + le16_to_cpu(event->code),
> + le32_to_cpu(event->value));
> + virtinput_queue_evtbuf(vi, event);
> + }
> + virtqueue_kick(vq);
> + mutex_unlock(&vi->lock);
> +}
> +
> +static int virtinput_send_status(struct virtio_input *vi,
> + u16 type, u16 code, s32 value)
> +{
> + struct virtio_input_event *stsbuf;
> + struct scatterlist sg[1];
> + int rc;
> +
> + stsbuf = kzalloc(sizeof(*stsbuf), GFP_ATOMIC);
> + if (!stsbuf)
> + return -ENOMEM;
> +
> + stsbuf->type = cpu_to_le16(type);
> + stsbuf->code = cpu_to_le16(code);
> + stsbuf->value = cpu_to_le32(value);
> + sg_init_one(sg, stsbuf, sizeof(*stsbuf));
> +
> + mutex_lock(&vi->lock);
> + rc = virtqueue_add_outbuf(vi->sts, sg, 1, stsbuf, GFP_ATOMIC);
> + virtqueue_kick(vi->sts);
> + mutex_unlock(&vi->lock);
> +
> + if (rc != 0)
> + kfree(stsbuf);
> + return rc;
> +}
> +
> +static void virtinput_recv_status(struct virtqueue *vq)
> +{
> + struct virtio_input *vi = vq->vdev->priv;
> + struct virtio_input_event *stsbuf;
> + unsigned int len;
> +
> + mutex_lock(&vi->lock);
> + while ((stsbuf = virtqueue_get_buf(vi->sts, &len)) != NULL)
> + kfree(stsbuf);
> + mutex_unlock(&vi->lock);
> +}
> +
> +static int virtinput_status(struct input_dev *idev, unsigned int type,
> + unsigned int code, int value)
> +{
> + struct virtio_input *vi = input_get_drvdata(idev);
> +
> + return virtinput_send_status(vi, type, code, value);
> +}
> +
> +static size_t virtinput_cfg_select(struct virtio_input *vi,
> + u8 select, u8 subsel)
> +{
> + u8 size;
> +
> + virtio_cwrite(vi->vdev, struct virtio_input_config, select, &select);
> + virtio_cwrite(vi->vdev, struct virtio_input_config, subsel, &subsel);
> + virtio_cread(vi->vdev, struct virtio_input_config, size, &size);
> + return size;
> +}
> +
> +static void virtinput_cfg_bits(struct virtio_input *vi, int select, int subsel,
> + unsigned long *bits, unsigned int bitcount)
> +{
> + unsigned int bit;
> + size_t bytes;
> + u8 cfg = 0;
> +
> + bytes = virtinput_cfg_select(vi, select, subsel);
> + if (!bytes)
> + return;
> + if (bitcount > bytes*8)
> + bitcount = bytes*8;
> + if (select == VIRTIO_INPUT_CFG_EV_BITS)
> + set_bit(subsel, vi->idev->evbit);
> + for (bit = 0; bit < bitcount; bit++) {
> + if ((bit % 8) == 0)
> + virtio_cread(vi->vdev, struct virtio_input_config,
> + u.bitmap[bit / 8], &cfg);
> + if (cfg & (1 << (bit % 8)))
> + set_bit(bit, bits);
> + }
> +}
> +
> +static void virtinput_cfg_abs(struct virtio_input *vi, int abs)
> +{
> + u32 mi, ma, fu, fl;
> +
> + virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ABS_INFO, abs);
> + virtio_cread(vi->vdev, struct virtio_input_config, u.abs.min, &mi);
> + virtio_cread(vi->vdev, struct virtio_input_config, u.abs.max, &ma);
> + virtio_cread(vi->vdev, struct virtio_input_config, u.abs.fuzz, &fu);
> + virtio_cread(vi->vdev, struct virtio_input_config, u.abs.flat, &fl);
> + input_set_abs_params(vi->idev, abs, mi, ma, fu, fl);
What about setting resolution as David suggested?
> +}
> +
> +static int virtinput_init_vqs(struct virtio_input *vi)
> +{
> + struct virtqueue *vqs[2];
> + vq_callback_t *cbs[] = { virtinput_recv_events,
> + virtinput_recv_status };
> + static const char * names[] = { "events", "status" };
> + int i, err, size;
> +
> + err = vi->vdev->config->find_vqs(vi->vdev, 2, vqs, cbs, names);
> + if (err)
> + return err;
> + vi->evt = vqs[0];
> + vi->sts = vqs[1];
> +
> + size = virtqueue_get_vring_size(vi->evt);
> + if (size > ARRAY_SIZE(vi->evts))
> + size = ARRAY_SIZE(vi->evts);
> + for (i = 0; i < size; i++)
> + virtinput_queue_evtbuf(vi, &vi->evts[i]);
> + virtqueue_kick(vi->evt);
> +
> + return 0;
> +}
> +
> +static int virtinput_probe(struct virtio_device *vdev)
> +{
> + struct virtio_input *vi;
> + size_t size;
> + int abs, err;
> +
> + vi = kzalloc(sizeof(*vi), GFP_KERNEL);
> + if (!vi)
> + return -ENOMEM;
> +
> + vdev->priv = vi;
> + vi->vdev = vdev;
> + mutex_init(&vi->lock);
> +
> + err = virtinput_init_vqs(vi);
> + if (err)
> + goto err_init_vq;
> +
> + vi->idev = input_allocate_device();
> + if (!vi->idev) {
> + err = -ENOMEM;
> + goto err_input_alloc;
> + }
> + input_set_drvdata(vi->idev, vi);
> +
> + size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_NAME, 0);
> + virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config, u),
> + vi->name, min(size, sizeof(vi->name)));
> + size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_SERIAL, 0);
> + virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config, u),
> + vi->serial, min(size, sizeof(vi->serial)));
> + snprintf(vi->phys, sizeof(vi->phys),
> + "virtio%d/input0", vdev->index);
> + vi->idev->name = vi->name;
> + vi->idev->phys = vi->phys;
> + vi->idev->uniq = vi->serial;
> +
> + size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_DEVIDS, 0);
> + if (size >= 8) {
> + virtio_cread(vi->vdev, struct virtio_input_config,
> + u.ids.bustype, &vi->idev->id.bustype);
> + virtio_cread(vi->vdev, struct virtio_input_config,
> + u.ids.vendor, &vi->idev->id.vendor);
> + virtio_cread(vi->vdev, struct virtio_input_config,
> + u.ids.product, &vi->idev->id.product);
> + virtio_cread(vi->vdev, struct virtio_input_config,
> + u.ids.version, &vi->idev->id.version);
> + } else {
> + vi->idev->id.bustype = BUS_VIRTUAL;
> + }
> +
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_PROP_BITS, 0,
> + vi->idev->propbit, INPUT_PROP_CNT);
> + size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_REP);
> + if (size)
> + set_bit(EV_REP, vi->idev->evbit);
> +
> + vi->idev->dev.parent = &vdev->dev;
> + vi->idev->dev.groups = dev_attr_groups;
> + vi->idev->event = virtinput_status;
> +
> + /* device -> kernel */
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_KEY,
> + vi->idev->keybit, KEY_CNT);
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_REL,
> + vi->idev->relbit, REL_CNT);
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_ABS,
> + vi->idev->absbit, ABS_CNT);
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_MSC,
> + vi->idev->mscbit, MSC_CNT);
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SW,
> + vi->idev->swbit, SW_CNT);
> +
> + /* kernel -> device */
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_LED,
> + vi->idev->ledbit, LED_CNT);
> + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SND,
> + vi->idev->sndbit, SND_CNT);
> +
> + if (test_bit(EV_ABS, vi->idev->evbit)) {
> + for (abs = 0; abs < ABS_CNT; abs++) {
> + if (!test_bit(abs, vi->idev->absbit))
> + continue;
> + virtinput_cfg_abs(vi, abs);
> + }
> + }
> + virtio_device_ready(vdev);
> +
> + err = input_register_device(vi->idev);
> + if (err)
> + goto err_input_register;
> +
> + return 0;
> +
> +err_input_register:
> + input_free_device(vi->idev);
> +err_input_alloc:
> + vdev->config->del_vqs(vdev);
> +err_init_vq:
> + kfree(vi);
> + return err;
> +}
> +
> +static void virtinput_remove(struct virtio_device *vdev)
> +{
> + struct virtio_input *vi = vdev->priv;
> +
> + input_unregister_device(vi->idev);
> + input_free_device(vi->idev);
Freeing input devices after unregistering is verboten.
> + vdev->config->del_vqs(vdev);
> + kfree(vi);
> +}
> +
> +static unsigned int features[] = {
> +};
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_INPUT, VIRTIO_DEV_ANY_ID },
> + { 0 },
> +};
> +
> +static struct virtio_driver virtio_input_driver = {
> + .driver.name = KBUILD_MODNAME,
> + .driver.owner = THIS_MODULE,
> + .feature_table = features,
> + .feature_table_size = ARRAY_SIZE(features),
> + .id_table = id_table,
> + .probe = virtinput_probe,
> + .remove = virtinput_remove,
> +};
> +
> +module_virtio_driver(virtio_input_driver);
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Virtio input device driver");
> +MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
> diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
> index 68ceb97..04b829e 100644
> --- a/include/uapi/linux/Kbuild
> +++ b/include/uapi/linux/Kbuild
> @@ -430,6 +430,7 @@ header-y += virtio_blk.h
> header-y += virtio_config.h
> header-y += virtio_console.h
> header-y += virtio_ids.h
> +header-y += virtio_input.h
> header-y += virtio_net.h
> header-y += virtio_pci.h
> header-y += virtio_ring.h
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index 284fc3a..5f60aa4 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -39,5 +39,6 @@
> #define VIRTIO_ID_9P 9 /* 9p virtio console */
> #define VIRTIO_ID_RPROC_SERIAL 11 /* virtio remoteproc serial link */
> #define VIRTIO_ID_CAIF 12 /* Virtio caif */
> +#define VIRTIO_ID_INPUT 18 /* virtio input */
>
> #endif /* _LINUX_VIRTIO_IDS_H */
> diff --git a/include/uapi/linux/virtio_input.h b/include/uapi/linux/virtio_input.h
> new file mode 100644
> index 0000000..9302422
> --- /dev/null
> +++ b/include/uapi/linux/virtio_input.h
> @@ -0,0 +1,75 @@
> +#ifndef _LINUX_VIRTIO_INPUT_H
> +#define _LINUX_VIRTIO_INPUT_H
> +/* This header is BSD licensed so anyone can use the definitions to implement
> + * compatible drivers/servers.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + * 3. Neither the name of IBM nor the names of its contributors
> + * may be used to endorse or promote products derived from this software
> + * without specific prior written permission.
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
> + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR
> + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
> + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
> + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
> + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
> + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> + * SUCH DAMAGE. */
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_config.h>
> +
> +enum virtio_input_config_select {
> + VIRTIO_INPUT_CFG_UNSET = 0x00,
> + VIRTIO_INPUT_CFG_ID_NAME = 0x01,
> + VIRTIO_INPUT_CFG_ID_SERIAL = 0x02,
> + VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03,
> + VIRTIO_INPUT_CFG_PROP_BITS = 0x10,
> + VIRTIO_INPUT_CFG_EV_BITS = 0x11,
> + VIRTIO_INPUT_CFG_ABS_INFO = 0x12,
> +};
> +
> +struct virtio_input_absinfo {
> + __u32 min;
> + __u32 max;
> + __u32 fuzz;
> + __u32 flat;
> +};
> +
> +struct virtio_input_devids {
> + __u16 bustype;
> + __u16 vendor;
> + __u16 product;
> + __u16 version;
> +};
> +
> +struct virtio_input_config {
> + __u8 select;
> + __u8 subsel;
> + __u8 size;
> + __u8 reserved;
> + union {
> + char string[128];
> + __u8 bitmap[128];
> + struct virtio_input_absinfo abs;
> + struct virtio_input_devids ids;
> + } u;
> +};
> +
> +struct virtio_input_event {
> + __le16 type;
> + __le16 code;
> + __le32 value;
> +};
> +
> +#endif /* _LINUX_VIRTIO_INPUT_H */
> --
> 1.8.3.1
>
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH v5] add support for Freescale's MMA8653FC 10 bit accelerometer
From: Martin Kepplinger @ 2015-03-21 11:09 UTC (permalink / raw)
To: benjamin.tissoires, mark.rutland, robh+dt, Pawel.Moll,
ijc+devicetree, galak, dmitry.torokhov, alexander.stein
Cc: hadess, akpm, gregkh, linux-api, devicetree, linux-input,
linux-kernel, Martin Kepplinger, Christoph Muellner
In-Reply-To: <1426870690-18365-1-git-send-email-martink@posteo.de>
From: Martin Kepplinger <martin.kepplinger@theobroma-systems.com>
The MMA8653FC is a low-power, three-axis, capacitive micromachined
accelerometer with 10 bits of resolution with flexible user-programmable
options.
Embedded interrupt functions enable overall power savings, by relieving the
host processor from continuously polling data, for example using the poll()
system call.
The device can be configured to generate wake-up interrupt signals from any
combination of the configurable embedded functions, enabling the MMA8653FC
to monitor events while remaining in a low-power mode during periods of
inactivity.
This driver provides devicetree properties to program the device's behaviour
and a simple, tested and documented sysfs interface. The data sheet and more
information is available on Freescale's website.
Signed-off-by: Martin Kepplinger <martin.kepplinger@theobroma-systems.com>
Signed-off-by: Christoph Muellner <christoph.muellner@theobroma-systems.com>
---
Sorry, I forgot the Kconfig integration into drivers/staging.
This applies and builds.
drivers/staging/Kconfig | 2 +
drivers/staging/mma8653fc/Kconfig | 10 +
drivers/staging/mma8653fc/Makefile | 1 +
drivers/staging/mma8653fc/TODO | 146 ++++++
drivers/staging/mma8653fc/mma8653fc.c | 864 ++++++++++++++++++++++++++++++++++
5 files changed, 1023 insertions(+)
create mode 100644 drivers/staging/mma8653fc/Kconfig
create mode 100644 drivers/staging/mma8653fc/Makefile
create mode 100644 drivers/staging/mma8653fc/TODO
create mode 100644 drivers/staging/mma8653fc/mma8653fc.c
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index bfacf69..834d949 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -112,4 +112,6 @@ source "drivers/staging/i2o/Kconfig"
source "drivers/staging/fsl-mc/Kconfig"
+source "drivers/staging/mma8653fc/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/mma8653fc/Kconfig b/drivers/staging/mma8653fc/Kconfig
new file mode 100644
index 0000000..988451b
--- /dev/null
+++ b/drivers/staging/mma8653fc/Kconfig
@@ -0,0 +1,10 @@
+config MMA8653FC
+ tristate "MMA8653FC - Freescale's 3-Axis, 10-bit Digital Accelerometer"
+ depends on I2C
+ default n
+ help
+ Say Y here if you want to support Freescale's MMA8653FC Accelerometer
+ through I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mma8653fc.
diff --git a/drivers/staging/mma8653fc/Makefile b/drivers/staging/mma8653fc/Makefile
new file mode 100644
index 0000000..9a245a3
--- /dev/null
+++ b/drivers/staging/mma8653fc/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MMA8653FC) += mma8653fc.o
diff --git a/drivers/staging/mma8653fc/TODO b/drivers/staging/mma8653fc/TODO
new file mode 100644
index 0000000..0a31225
--- /dev/null
+++ b/drivers/staging/mma8653fc/TODO
@@ -0,0 +1,146 @@
+- move to IIO device API. The current DT/sysfs interface is documented below
+
+Documentation/ABI/testing/...
+-----------------------------
+What: /sys/bus/i2c/drivers/mma8653fc/*/standby
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger@theobroma-systems.com>
+Description:
+ Write 0 to this in order to turn on the device, and 1 to turn
+ it off. Read to see if it is turned on or off.
+
+
+What: /sys/bus/i2c/drivers/mma8653fc/*/currentmode
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger@theobroma-systems.com>
+Description:
+ Reading this provides the current state of the device, read
+ directly from a register. This can be "standby", "wake" or
+ "sleep".
+
+
+What: /sys/bus/i2c/drivers/mma8653fc/*/position
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger@theobroma-systems.com>
+Description:
+ Read only. Without interrupts enabled gets current position
+ values by reading. Poll "position" with interrupt conditions
+ set, to get notified; see Documentation/.../fsl,mma8653fc.txt
+
+ position file format:
+ "x y z [landscape/portrait status] [front/back status]"
+
+ x y z values:
+ in mg
+ landscape/portrait status char:
+ r landscape right
+ d portrait down
+ u portrait up
+ l landscape left
+ front/back status char:
+ f front facing
+ b back facing
+
+
+Documentation/devicetree/bindings/...
+-------------------------------------
+Required properties:
+- compatible
+ "fsl,mma8653fc"
+- reg
+ I2C address
+
+Optional properties:
+
+- interrupt-parent
+ a phandle for the interrupt controller (see
+ Documentation/devicetree/bindings/interrupt-controller/interrupts.txt)
+- interrupts
+ interrupt line to which the chip is connected (active low)
+- int1
+ set to use interrupt line 1, default is line 2
+ the interrupt sources can be routed to one of the two lines
+- ir-freefall-motion-x
+ activate freefall/motion interrupts on x axis
+- ir-freefall-motion-y
+ activate freefall/motion interrupts on y axis
+- ir-freefall-motion-z
+ activate freefall/motion interrupts on z axis
+- irq-threshold
+ 0 < value < 8000: threshold for motion interrupts in mg
+- ir-landscape-portrait
+ activate landscape/portrait interrupts
+- ir-auto-wake
+ activate wake/sleep change interrupts
+- ir-data-ready:
+ activate data-ready interrupts
+ Interrupt events can be activated in any combination.
+- dynamic-range
+ 2, 4, or 8: dynamic measurement range in g, default: 2
+ In ±2 g mode, sensitivity = 256 counts/g.
+ In ±4 g mode, sensitivity = 128 counts/g.
+ In ±8 g mode, sensitivity = 64 counts/g.
+- auto-wake-sleep
+ auto sleep mode (lower frequency)
+- motion-mode
+ use motion mode instead of freefall mode (trigger if >threshold).
+ per default an interrupt occurs if motion values fall below the
+ value set in "threshold" and therefore can detect free fall on the
+ vertical axis (depending on the position of the device).
+ Setting this values inverts the behaviour and an interrupt occurs
+ above the threshold value, so usually activate horizontal axis in
+ this case.
+
+- x-offset
+ 0 < value < 500: calibration offset in mg
+ The offset correction values are used to realign the Zero-g position
+ of the X, Y, and Z-axis after the device is mounted on a board.
+ this value has an offset of 250 itself:
+ 0 is -250mg, 250 is 0 mg, 500 is 250mg
+- y-offset
+ see x-offset
+- z-offset
+ see x-offset
+
+Example 1:
+for a device laying on flat ground to recognize acceleration over 100mg.
+x-axis is calibrated to +10mg. Adapt interrupt line to your device.
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ interrupt-parent = <&gpio1>;
+ interrupts = <5 0>;
+ reg = <0x1d>;
+
+ dynamic-range = <2>;
+ motion-mode;
+ ir-freefall-motion-x;
+ ir-freefall-motion-y;
+ irq-threshold = <100>;
+ x-offset = <160>;
+};
+
+Example 2:
+for a device mounted on a wall with y being the vertical axis. This recognizes
+y-acceleration below 800mg, so free fall or changing the orientation of the
+device (y not being the vertical axis and having ~1000mg anymore).
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ interrupt-parent = <&gpio1>;
+ interrupts = <5 0>;
+ reg = <0x1d>;
+
+ dynamic-range = <2>;
+ ir-freefall-motion-y;
+ irq-threshold = <800>;
+};
+
+Example 3:
+minimal example. lets users read current acceleration values. No polling
+is available.
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ reg = <0x1d>;
+};
diff --git a/drivers/staging/mma8653fc/mma8653fc.c b/drivers/staging/mma8653fc/mma8653fc.c
new file mode 100644
index 0000000..4bd7f99
--- /dev/null
+++ b/drivers/staging/mma8653fc/mma8653fc.c
@@ -0,0 +1,864 @@
+/*
+ * mma8653fc.c - Support for Freescale MMA8653FC 3-axis 10-bit accelerometer
+ *
+ * Copyright (c) 2014 Theobroma Systems Design and Consulting 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.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+
+#define DRV_NAME "mma8653fc"
+#define MMA8653FC_DEVICE_ID 0x5a
+
+#define MMA8653FC_STATUS 0x00
+
+#define ZYXOW_MASK 0x80
+#define ZYXDR 0x08
+
+#define MMA8653FC_WHO_AM_I 0x0d
+
+#define MMA8653FC_SYSMOD 0x0b
+#define STATE_STANDBY 0x00
+#define STATE_WAKE 0x01
+#define STATE_SLEEP 0x02
+
+#define MMA8450_STATUS_ZXYDR 0x08
+
+/*
+ * 10 bit output data registers
+ * MSB: 7:0 bits 9:2 of data word
+ * LSB: 7:6 bits 1:0 of data word
+ */
+#define MMA8653FC_OUT_X_MSB 0x01
+#define MMA8653FC_OUT_X_LSB 0x02
+#define MMA8653FC_OUT_Y_MSB 0x03
+#define MMA8653FC_OUT_Y_LSB 0x04
+#define MMA8653FC_OUT_Z_MSB 0x05
+#define MMA8653FC_OUT_Z_LSB 0x06
+
+/*
+ * Portrait/Landscape Status
+ */
+#define MMA8653FC_PL_STATUS 0x10
+
+#define NEWLP 0x80
+#define LAPO_HIGH 0x04
+#define LAPO_LOW 0x02
+#define BAFRO 0x01
+
+/*
+ * Portrait/Landscape Configuration
+ */
+#define MMA8653FC_PL_CFG 0x11
+
+#define PL_EN (1 << 6)
+
+/*
+ * Data calibration registers
+ */
+#define MMA8653FC_OFF_X 0x2f
+#define MMA8653FC_OFF_Y 0x30
+#define MMA8653FC_OFF_Z 0x31
+
+/* 0 to 500 for dts, but -250 to 250 in mg */
+#define DEFAULT_OFF 250
+
+/*
+ * bits 1:0 dynamic range
+ * 00 +/- 2g
+ * 01 +/- 4g
+ * 10 +/- 8g
+ *
+ * HPF_Out bit 4 - data high pass or low pass filtered
+ */
+#define MMA8653FC_XYZ_DATA_CFG 0x0e
+
+#define RANGE_MASK 0x03
+#define RANGE2G 0x00
+#define RANGE4G 0x01
+#define RANGE8G 0x02
+/* values for calculation */
+#define SHIFT_2G 8
+#define INCR_2G 128
+#define SHIFT_4G 7
+#define INCR_4G 64
+#define SHIFT_8G 6
+#define INCR_8G 32
+#define DYN_RANGE_2G 2
+#define DYN_RANGE_4G 4
+#define DYN_RANGE_8G 8
+
+/*
+ * System Control Reg 1
+ */
+#define MMA8653FC_CTRL_REG1 0x2a
+
+#define ACTIVE_BIT (1 << 0)
+#define ODR_MASK 0x38
+#define ODR_DEFAULT 0x20 /* 50 Hz */
+#define ASLP_RATE_MASK 0xc0
+#define ASLP_RATE_DEFAULT 0x80 /* 6.25 Hz */
+
+/*
+ * Sys Control Reg 2
+ *
+ * auto-sleep enable, software reset
+ */
+#define MMA8653FC_CTRL_REG2 0x2b
+
+#define SLPE (1 << 2)
+#define SELFTEST (1 << 7)
+#define SOFT_RESET (1 << 6)
+
+/*
+ * Interrupt Source
+ */
+#define MMA8653FC_INT_SOURCE 0x0c
+
+#define SRC_ASLP (1 << 7)
+#define SRC_LNDPRT (1 << 4)
+#define SRC_FF_MT (1 << 2)
+#define SRC_DRDY (1 << 0)
+
+/*
+ * Interrupt Control Register
+ *
+ * default: active low
+ * default push-pull, not open-drain
+ */
+#define MMA8653FC_CTRL_REG3 0x2c
+
+#define WAKE_LNDPRT (1 << 5)
+#define WAKE_FF_MT (1 << 3)
+#define IPOL (1 << 1)
+#define PP_OD (1 << 0)
+
+/*
+ * Interrupt Enable Register
+ */
+#define MMA8653FC_CTRL_REG4 0x2d
+
+#define INT_EN_ASLP (1 << 7)
+#define INT_EN_LNDPRT (1 << 4)
+#define INT_EN_FF_MT (1 << 2)
+#define INT_EN_DRDY (1 << 0)
+
+/*
+ * Interrupt Configuration Register
+ * bit value 0 ... INT2 (default)
+ * bit value 1 ... INT1
+ */
+#define MMA8653FC_CTRL_REG5 0x2e
+
+#define INT_CFG_ASLP (1 << 7)
+#define INT_CFG_LNDPRT (1 << 4)
+#define INT_CFG_FF_MT (1 << 2)
+#define INT_CFG_DRDY (1 << 0)
+
+/*
+ * Freefall / Motion Configuration Register
+ *
+ * Event Latch enable/disable, motion or freefall mode
+ * and event flag enable per axis
+ */
+#define MMA8653FC_FF_MT_CFG 0x15
+
+#define FF_MT_CFG_ELE (1 << 7)
+#define FF_MT_CFG_OAE (1 << 6)
+#define FF_MT_CFG_ZEFE (1 << 5)
+#define FF_MT_CFG_YEFE (1 << 4)
+#define FF_MT_CFG_XEFE (1 << 3)
+
+/*
+ * Freefall / Motion Source Register
+ */
+#define MMA8653FC_FF_MT_SRC 0x16
+
+/*
+ * Freefall / Motion Threshold Register
+ *
+ * define motion threshold
+ * 0.063 g/LSB, 127 counts(0x7f) (7 bit from LSB)
+ * range: 0.063g - 8g
+ */
+#define MMA8653FC_FF_MT_THS 0x17
+
+struct axis_triple {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+struct mma8653fc_pdata {
+ s8 x_axis_offset;
+ s8 y_axis_offset;
+ s8 z_axis_offset;
+ bool auto_wake_sleep;
+ u32 range;
+ bool int1;
+ bool motion_mode;
+ u8 freefall_motion_thr;
+ bool int_src_data_ready;
+ bool int_src_ff_mt_x;
+ bool int_src_ff_mt_y;
+ bool int_src_ff_mt_z;
+ bool int_src_lndprt;
+ bool int_src_aslp;
+};
+
+struct mma8653fc {
+ struct i2c_client *client;
+ struct mutex mutex;
+ struct mma8653fc_pdata pdata;
+ struct axis_triple saved;
+ char orientation;
+ char bafro;
+ bool standby;
+ int irq;
+ unsigned int_mask;
+ u8 (*read)(struct mma8653fc *, unsigned char);
+ void (*write)(struct mma8653fc *, unsigned char, unsigned char);
+};
+
+/* defaults */
+static const struct mma8653fc_pdata mma8653fc_default_init = {
+ .range = 2,
+ .x_axis_offset = DEFAULT_OFF,
+ .y_axis_offset = DEFAULT_OFF,
+ .z_axis_offset = DEFAULT_OFF,
+ .auto_wake_sleep = false,
+ .int1 = false,
+ .motion_mode = false,
+ .freefall_motion_thr = 5,
+ .int_src_data_ready = false,
+ .int_src_ff_mt_x = false,
+ .int_src_ff_mt_y = false,
+ .int_src_ff_mt_z = false,
+ .int_src_lndprt = false,
+ .int_src_aslp = false,
+};
+
+static void mma8653fc_get_triple(struct mma8653fc *mma)
+{
+ u8 buf[6];
+ u8 status;
+
+ buf[0] = 0;
+
+ status = mma->read(mma, MMA8653FC_STATUS);
+ if (status & ZYXOW_MASK)
+ dev_dbg(&mma->client->dev, "previous read not completed\n");
+
+ buf[0] = mma->read(mma, MMA8653FC_OUT_X_MSB);
+ buf[1] = mma->read(mma, MMA8653FC_OUT_X_LSB);
+ buf[2] = mma->read(mma, MMA8653FC_OUT_Y_MSB);
+ buf[3] = mma->read(mma, MMA8653FC_OUT_Y_LSB);
+ buf[4] = mma->read(mma, MMA8653FC_OUT_Z_MSB);
+ buf[5] = mma->read(mma, MMA8653FC_OUT_Z_LSB);
+
+ mutex_lock(&mma->mutex);
+ /* move from registers to s16 */
+ mma->saved.x = (buf[1] | (buf[0] << 8)) >> 6;
+ mma->saved.y = (buf[3] | (buf[2] << 8)) >> 6;
+ mma->saved.z = (buf[5] | (buf[4] << 8)) >> 6;
+ mma->saved.x = sign_extend32(mma->saved.x, 9);
+ mma->saved.y = sign_extend32(mma->saved.y, 9);
+ mma->saved.z = sign_extend32(mma->saved.z, 9);
+
+ /* calc g, see data sheet and application note */
+ switch (mma->pdata.range) {
+ case DYN_RANGE_2G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_2G) >> SHIFT_2G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_2G) >> SHIFT_2G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_2G) >> SHIFT_2G);
+ break;
+ case DYN_RANGE_4G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_4G) >> SHIFT_4G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_4G) >> SHIFT_4G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_4G) >> SHIFT_4G);
+ break;
+ case DYN_RANGE_8G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_8G) >> SHIFT_8G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_8G) >> SHIFT_8G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_8G) >> SHIFT_8G);
+ break;
+ default:
+ dev_err(&mma->client->dev, "internal data corrupt\n");
+ }
+ mutex_unlock(&mma->mutex);
+}
+
+static void mma8653fc_get_orientation(struct mma8653fc *mma, u8 byte)
+{
+ if ((byte & LAPO_HIGH) && !(LAPO_LOW))
+ mma->orientation = 'r'; /* landscape right */
+ if (!(byte & LAPO_HIGH) && (byte & LAPO_LOW))
+ mma->orientation = 'd'; /* portrait down */
+ if (!(byte & LAPO_HIGH) && !(byte & LAPO_LOW))
+ mma->orientation = 'u'; /* portrait up */
+ if ((byte & LAPO_HIGH) && (byte & LAPO_LOW))
+ mma->orientation = 'l'; /* landscape left */
+
+ if (byte & BAFRO)
+ mma->bafro = 'b'; /* back facing */
+ else
+ mma->bafro = 'f'; /* front facing */
+}
+
+static irqreturn_t mma8653fc_irq(int irq, void *handle)
+{
+ struct mma8653fc *mma = handle;
+ u8 int_src;
+ u8 byte;
+
+ int_src = mma->read(mma, MMA8653FC_INT_SOURCE);
+ if (int_src & SRC_DRDY)
+ /* data ready handle */
+ if (int_src & SRC_FF_MT) {
+ /* freefall/motion change handle */
+ dev_dbg(&mma->client->dev,
+ "freefall or motion change\n");
+ byte = mma->read(mma, MMA8653FC_FF_MT_SRC);
+ }
+ if (int_src & SRC_LNDPRT) {
+ /* landscape/portrait change handle */
+ dev_dbg(&mma->client->dev,
+ "landscape / portrait change\n");
+ byte = mma->read(mma, MMA8653FC_PL_STATUS);
+ mma8653fc_get_orientation(mma, byte);
+ }
+ if (int_src & SRC_ASLP)
+ /* autosleep change handle */
+ mma8653fc_get_triple(mma);
+
+ sysfs_notify(&mma->client->dev.kobj, NULL, "position");
+
+ return IRQ_HANDLED;
+}
+
+static void __mma8653fc_enable(struct mma8653fc *mma)
+{
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte | ACTIVE_BIT);
+}
+
+static void __mma8653fc_disable(struct mma8653fc *mma)
+{
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte & ~ACTIVE_BIT);
+}
+
+static ssize_t mma8653fc_standby_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", mma->standby);
+}
+
+static ssize_t mma8653fc_standby_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&mma->mutex);
+
+ if (val) {
+ if (!mma->standby)
+ __mma8653fc_disable(mma);
+ } else {
+ if (mma->standby)
+ __mma8653fc_enable(mma);
+ }
+
+ mma->standby = !!val;
+
+ mutex_unlock(&mma->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(standby, 0664, mma8653fc_standby_show,
+ mma8653fc_standby_store);
+
+static ssize_t mma8653fc_currentmode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ ssize_t count;
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_SYSMOD);
+ if (byte < 0)
+ return byte;
+
+ switch (byte) {
+ case STATE_STANDBY:
+ count = scnprintf(buf, PAGE_SIZE, "standby\n");
+ break;
+ case STATE_WAKE:
+ count = scnprintf(buf, PAGE_SIZE, "wake\n");
+ break;
+ case STATE_SLEEP:
+ count = scnprintf(buf, PAGE_SIZE, "sleep\n");
+ break;
+ default:
+ count = scnprintf(buf, PAGE_SIZE, "unknown\n");
+ }
+ return count;
+}
+static DEVICE_ATTR(currentmode, S_IRUGO, mma8653fc_currentmode_show, NULL);
+
+static ssize_t mma8653fc_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ ssize_t count;
+ u8 byte;
+
+ if (!mma->irq) {
+ byte = mma->read(mma, MMA8653FC_PL_STATUS);
+ if (byte & NEWLP)
+ mma8653fc_get_orientation(mma, byte);
+
+ byte = mma->read(mma, MMA8653FC_STATUS);
+ if (byte & ZYXDR)
+ mma8653fc_get_triple(mma);
+
+ msleep(20);
+ dev_dbg(&client->dev, "data polled\n");
+ }
+ mutex_lock(&mma->mutex);
+ count = scnprintf(buf, PAGE_SIZE, "%d %d %d %c %c\n",
+ mma->saved.x, mma->saved.y, mma->saved.z,
+ mma->orientation, mma->bafro);
+ mutex_unlock(&mma->mutex);
+
+ return count;
+}
+static DEVICE_ATTR(position, S_IRUGO, mma8653fc_position_show, NULL);
+
+static struct attribute *mma8653fc_attributes[] = {
+ &dev_attr_position.attr,
+ &dev_attr_standby.attr,
+ &dev_attr_currentmode.attr,
+ NULL,
+};
+
+static const struct attribute_group mma8653fc_attr_group = {
+ .attrs = mma8653fc_attributes,
+};
+
+static u8 mma8653fc_read(struct mma8653fc *mma, unsigned char reg)
+{
+ u8 val;
+
+ val = i2c_smbus_read_byte_data(mma->client, reg);
+ if (val < 0) {
+ dev_err(&mma->client->dev,
+ "failed to read %x register\n", reg);
+ }
+ return val;
+}
+
+static void mma8653fc_write(struct mma8653fc *mma, unsigned char reg,
+ unsigned char val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(mma->client, reg, val);
+ if (ret) {
+ dev_err(&mma->client->dev,
+ "failed to write %x register\n", reg);
+ }
+}
+
+static const struct of_device_id mma8653fc_dt_ids[] = {
+ { .compatible = "fsl,mma8653fc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mma8653fc_dt_ids);
+
+static const struct mma8653fc_pdata *mma8653fc_probe_dt(struct device *dev)
+{
+ struct mma8653fc_pdata *pdata;
+ struct device_node *node = dev->of_node;
+ const struct of_device_id *match;
+ u32 testu32;
+ s32 tests32;
+
+ if (!node) {
+ dev_err(dev, "no associated DT data\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ match = of_match_device(mma8653fc_dt_ids, dev);
+ if (!match) {
+ dev_err(dev, "unknown device model\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ *pdata = mma8653fc_default_init;
+
+ /* overwrite from dts */
+ testu32 = pdata->x_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "x-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on X axis\n", tests32);
+ /* calc register value, resolution: 1.96mg */
+ pdata->x_axis_offset = (s8) (tests32 / 2);
+ }
+ testu32 = pdata->y_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "y-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on Y axis\n", tests32);
+ pdata->y_axis_offset = (s8) (tests32 / 2);
+ }
+ testu32 = pdata->z_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "z-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on Z axis\n", tests32);
+ pdata->z_axis_offset = (s8) (tests32 / 2);
+ }
+
+ testu32 = 0;
+ of_property_read_u32(node, "dynamic-range", &testu32);
+ if ((testu32) && (testu32 != 2) && (testu32 != 4) && (testu32 != 8)) {
+ dev_warn(dev, "wrong value for full scale range in dtb\n");
+ } else {
+ if (testu32)
+ pdata->range = testu32;
+ }
+
+ if (of_property_read_bool(node, "auto-wake-sleep"))
+ pdata->auto_wake_sleep = true;
+
+ if (of_property_read_bool(node, "int1"))
+ pdata->int1 = true;
+
+ if (of_property_read_bool(node, "motion-mode"))
+ pdata->motion_mode = true;
+
+ if (of_property_read_bool(node, "ir-data-ready"))
+ pdata->int_src_data_ready = true;
+
+ if (of_property_read_bool(node, "ir-freefall-motion-x"))
+ pdata->int_src_ff_mt_x = true;
+
+ if (of_property_read_bool(node, "ir-freefall-motion-y"))
+ pdata->int_src_ff_mt_y = true;
+
+ if (of_property_read_bool(node, "ir-freefall-motion-z"))
+ pdata->int_src_ff_mt_z = true;
+
+ if (of_property_read_bool(node, "ir-auto-wake"))
+ pdata->int_src_aslp = true;
+
+ if (of_property_read_bool(node, "ir-landscape-portrait"))
+ pdata->int_src_lndprt = true;
+
+ testu32 = 0;
+ of_property_read_u32(node, "irq-threshold", &testu32);
+ /* always uses maximum range +/- 8000g, resolution 63mg */
+ if ((testu32 <= 8000) && (testu32 > 0)) {
+ dev_dbg(dev, "use freefall / motion threshold %dmg\n",
+ testu32);
+ /* calculate register value from mg */
+ pdata->freefall_motion_thr = (testu32 / 63) + 1;
+ }
+
+ return pdata;
+}
+static int mma8653fc_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mma8653fc *mma;
+ const struct mma8653fc_pdata *pdata;
+ int err;
+ u8 byte;
+
+ err = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA);
+ if (!err) {
+ dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
+ return -EIO;
+ }
+
+ mma = devm_kzalloc(&client->dev, sizeof(*mma), GFP_KERNEL);
+ if (!mma)
+ err = -ENOMEM;
+
+ pdata = dev_get_platdata(&client->dev);
+ if (!pdata) {
+ pdata = mma8653fc_probe_dt(&client->dev);
+ if (IS_ERR(pdata)) {
+ err = PTR_ERR(pdata);
+ dev_err(&client->dev, "pdata from DT failed\n");
+ return err;
+ }
+ }
+ mma->pdata = *pdata;
+ pdata = &mma->pdata;
+ mma->client = client;
+ mma->read = &mma8653fc_read;
+ mma->write = &mma8653fc_write;
+
+ mutex_init(&mma->mutex);
+
+ i2c_set_clientdata(client, mma);
+
+ err = sysfs_create_group(&client->dev.kobj, &mma8653fc_attr_group);
+ if (err)
+ return err;
+
+ byte = mma->read(mma, MMA8653FC_WHO_AM_I);
+ if (byte != MMA8653FC_DEVICE_ID) {
+ dev_err(&client->dev, "wrong device for driver\n");
+ return -ENODEV;
+ }
+ dev_info(&client->dev, "loading driver for device id %x\n", byte);
+
+ mma->irq = irq_of_parse_and_map(client->dev.of_node, 0);
+ if (!mma->irq) {
+ dev_err(&client->dev, "Unable to parse or map IRQ\n");
+ goto no_irq;
+ }
+
+ err = irq_set_irq_type(mma->irq, IRQ_TYPE_EDGE_FALLING);
+ if (err) {
+ dev_err(&client->dev, "set_irq_type failed\n");
+ return err;
+ }
+
+ err = devm_request_threaded_irq(&client->dev, mma->irq, NULL,
+ mma8653fc_irq, IRQF_ONESHOT,
+ dev_name(&mma->client->dev), mma);
+ if (err) {
+ dev_err(&client->dev, "irq %d busy?\n", mma->irq);
+ return err;
+ }
+
+ mma->write(mma, MMA8653FC_CTRL_REG2, SOFT_RESET);
+
+ __mma8653fc_disable(mma);
+ mma->standby = true;
+
+ /* enable desired interrupts */
+ mma->orientation = '\0';
+ mma->bafro = '\0';
+ byte = 0;
+ if (pdata->int_src_data_ready) {
+ byte |= INT_EN_DRDY;
+ dev_dbg(&client->dev, "DATA READY interrupt source enabled\n");
+ }
+ if (pdata->int_src_ff_mt_x || pdata->int_src_ff_mt_y ||
+ pdata->int_src_ff_mt_z) {
+ byte |= INT_EN_FF_MT;
+ dev_dbg(&client->dev, "FF MT interrupt source enabled\n");
+ }
+ if (pdata->int_src_lndprt) {
+ mma->write(mma, MMA8653FC_PL_CFG, PL_EN);
+ byte |= INT_EN_LNDPRT;
+ dev_dbg(&client->dev, "LNDPRT interrupt source enabled\n");
+ }
+ if (pdata->int_src_aslp) {
+ byte |= INT_EN_ASLP;
+ dev_dbg(&client->dev, "ASLP interrupt source enabled\n");
+ }
+ mma->write(mma, MMA8653FC_CTRL_REG4, byte);
+
+ /* force everything to line 1 */
+ if (pdata->int1) {
+ mma->write(mma, MMA8653FC_CTRL_REG5,
+ (INT_CFG_ASLP | INT_CFG_LNDPRT |
+ INT_CFG_FF_MT | INT_CFG_DRDY));
+ dev_dbg(&client->dev, "using interrupt line 1\n");
+ }
+no_irq:
+ /* range mode */
+ byte = mma->read(mma, MMA8653FC_XYZ_DATA_CFG);
+ byte &= ~RANGE_MASK;
+ switch (pdata->range) {
+ case DYN_RANGE_2G:
+ byte |= RANGE2G;
+ dev_dbg(&client->dev, "use 2g range\n");
+ break;
+ case DYN_RANGE_4G:
+ byte |= RANGE4G;
+ dev_dbg(&client->dev, "use 4g range\n");
+ break;
+ case DYN_RANGE_8G:
+ byte |= RANGE8G;
+ dev_dbg(&client->dev, "use 8g range\n");
+ break;
+ default:
+ dev_err(&client->dev, "wrong range mode value\n");
+ return -EINVAL;
+ }
+ mma->write(mma, MMA8653FC_XYZ_DATA_CFG, byte);
+
+ /* data calibration offsets */
+ if (pdata->x_axis_offset)
+ mma->write(mma, MMA8653FC_OFF_X, pdata->x_axis_offset);
+ if (pdata->y_axis_offset)
+ mma->write(mma, MMA8653FC_OFF_Y, pdata->y_axis_offset);
+ if (pdata->z_axis_offset)
+ mma->write(mma, MMA8653FC_OFF_Z, pdata->z_axis_offset);
+
+ /* if autosleep, wake on both landscape and motion changes */
+ if (pdata->auto_wake_sleep) {
+ byte = 0;
+ byte |= WAKE_LNDPRT;
+ byte |= WAKE_FF_MT;
+ mma->write(mma, MMA8653FC_CTRL_REG3, byte);
+ mma->write(mma, MMA8653FC_CTRL_REG2, SLPE);
+ dev_dbg(&client->dev, "auto sleep enabled\n");
+ }
+
+ /* data rates */
+ byte = 0;
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ byte &= ~ODR_MASK;
+ byte |= ODR_DEFAULT;
+ byte &= ~ASLP_RATE_MASK;
+ byte |= ASLP_RATE_DEFAULT;
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte);
+
+ /* freefall / motion config */
+ byte = 0;
+ if (pdata->motion_mode) {
+ byte |= FF_MT_CFG_OAE;
+ dev_dbg(&client->dev, "detect motion instead of freefall\n");
+ }
+ byte |= FF_MT_CFG_ELE;
+ if (pdata->int_src_ff_mt_x)
+ byte |= FF_MT_CFG_XEFE;
+ if (pdata->int_src_ff_mt_y)
+ byte |= FF_MT_CFG_YEFE;
+ if (pdata->int_src_ff_mt_z)
+ byte |= FF_MT_CFG_ZEFE;
+ mma->write(mma, MMA8653FC_FF_MT_CFG, byte);
+
+ if (pdata->freefall_motion_thr) {
+ mma->write(mma, MMA8653FC_FF_MT_THS,
+ pdata->freefall_motion_thr);
+ /* calculate back to mg */
+ dev_dbg(&client->dev, "threshold set to %dmg\n",
+ (63 * pdata->freefall_motion_thr) - 1);
+ }
+
+ return 0;
+}
+
+static int mma8653fc_remove(struct i2c_client *client)
+{
+ dev_dbg(&client->dev, "unregistered accelerometer\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mma8653fc_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ __mma8653fc_disable(mma);
+
+ return 0;
+}
+
+static int mma8653fc_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ __mma8653fc_enable(mma);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mma8653fc_pm_ops, mma8653fc_suspend, mma8653fc_resume);
+#define MMA8653FC_PM_OPS (&mma8653fc_pm_ops)
+#else
+#define MMA8653FC_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id mma8653fc_id[] = {
+ { DRV_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mma8653fc_id);
+
+static struct i2c_driver mma8653fc_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = mma8653fc_dt_ids,
+ .pm = MMA8653FC_PM_OPS,
+ },
+ .probe = mma8653fc_i2c_probe,
+ .remove = mma8653fc_remove,
+ .id_table = mma8653fc_id,
+};
+
+module_i2c_driver(mma8653fc_driver);
+
+MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@theobroma-systems.com");
+MODULE_DESCRIPTION("Freescale's MMA8653FC Three-Axis Accelerometer I2C Driver");
+MODULE_LICENSE("GPL");
--
2.1.4
^ permalink raw reply related
* [PATCH v5] add support for Freescale's MMA8653FC 10 bit accelerometer
From: Martin Kepplinger @ 2015-03-21 11:26 UTC (permalink / raw)
To: benjamin.tissoires-Re5JQEeQqe8AvxtiuMwx3w,
mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
Pawel.Moll-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg,
galak-sgV2jX0FEOL9JmXXK+q4OQ,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
alexander.stein-93q1YBGzJSMe9JSWTWOYM3xStJ4P+DSV
Cc: hadess-0MeiytkfxGOsTnJN9+BGXg,
akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b,
gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r,
linux-api-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Martin Kepplinger,
Christoph Muellner
In-Reply-To: <1426936150-2439-1-git-send-email-martink-1KBjaw7Xf1+zQB+pC5nmwQ@public.gmane.org>
From: Martin Kepplinger <martin.kepplinger-SN7IsUiht6C/RdPyistoZJqQE7yCjDx5@public.gmane.org>
The MMA8653FC is a low-power, three-axis, capacitive micromachined
accelerometer with 10 bits of resolution with flexible user-programmable
options.
Embedded interrupt functions enable overall power savings, by relieving the
host processor from continuously polling data, for example using the poll()
system call.
The device can be configured to generate wake-up interrupt signals from any
combination of the configurable embedded functions, enabling the MMA8653FC
to monitor events while remaining in a low-power mode during periods of
inactivity.
This driver provides devicetree properties to program the device's behaviour
and a simple, tested and documented sysfs interface. The data sheet and more
information is available on Freescale's website.
Signed-off-by: Martin Kepplinger <martin.kepplinger@theobroma-systems.com>
Signed-off-by: Christoph Muellner <christoph.muellner@theobroma-systems.com>
---
Still, I was missing the drivers/staging Makefile addition. This applies and
builds automatically.
drivers/staging/Kconfig | 2 +
drivers/staging/Makefile | 1 +
drivers/staging/mma8653fc/Kconfig | 10 +
drivers/staging/mma8653fc/Makefile | 1 +
drivers/staging/mma8653fc/TODO | 146 ++++++
drivers/staging/mma8653fc/mma8653fc.c | 864 ++++++++++++++++++++++++++++++++++
6 files changed, 1024 insertions(+)
create mode 100644 drivers/staging/mma8653fc/Kconfig
create mode 100644 drivers/staging/mma8653fc/Makefile
create mode 100644 drivers/staging/mma8653fc/TODO
create mode 100644 drivers/staging/mma8653fc/mma8653fc.c
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index bfacf69..834d949 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -112,4 +112,6 @@ source "drivers/staging/i2o/Kconfig"
source "drivers/staging/fsl-mc/Kconfig"
+source "drivers/staging/mma8653fc/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 2bbd1bf..cfea86a 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -48,3 +48,4 @@ obj-$(CONFIG_COMMON_CLK_XLNX_CLKWZRD) += clocking-wizard/
obj-$(CONFIG_FB_TFT) += fbtft/
obj-$(CONFIG_I2O) += i2o/
obj-$(CONFIG_FSL_MC_BUS) += fsl-mc/
+obj-$(CONFIG_MMA8653FC) += mma8653fc/
diff --git a/drivers/staging/mma8653fc/Kconfig b/drivers/staging/mma8653fc/Kconfig
new file mode 100644
index 0000000..988451b
--- /dev/null
+++ b/drivers/staging/mma8653fc/Kconfig
@@ -0,0 +1,10 @@
+config MMA8653FC
+ tristate "MMA8653FC - Freescale's 3-Axis, 10-bit Digital Accelerometer"
+ depends on I2C
+ default n
+ help
+ Say Y here if you want to support Freescale's MMA8653FC Accelerometer
+ through I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mma8653fc.
diff --git a/drivers/staging/mma8653fc/Makefile b/drivers/staging/mma8653fc/Makefile
new file mode 100644
index 0000000..9a245a3
--- /dev/null
+++ b/drivers/staging/mma8653fc/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MMA8653FC) += mma8653fc.o
diff --git a/drivers/staging/mma8653fc/TODO b/drivers/staging/mma8653fc/TODO
new file mode 100644
index 0000000..0a31225
--- /dev/null
+++ b/drivers/staging/mma8653fc/TODO
@@ -0,0 +1,146 @@
+- move to IIO device API. The current DT/sysfs interface is documented below
+
+Documentation/ABI/testing/...
+-----------------------------
+What: /sys/bus/i2c/drivers/mma8653fc/*/standby
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger-SN7IsUiht6C/RdPyistoZJqQE7yCjDx5@public.gmane.org>
+Description:
+ Write 0 to this in order to turn on the device, and 1 to turn
+ it off. Read to see if it is turned on or off.
+
+
+What: /sys/bus/i2c/drivers/mma8653fc/*/currentmode
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger-SN7IsUiht6C/RdPyistoZJqQE7yCjDx5@public.gmane.org>
+Description:
+ Reading this provides the current state of the device, read
+ directly from a register. This can be "standby", "wake" or
+ "sleep".
+
+
+What: /sys/bus/i2c/drivers/mma8653fc/*/position
+Date: March 2015
+Contact: Martin Kepplinger <martin.kepplinger-SN7IsUiht6C/RdPyistoZJqQE7yCjDx5@public.gmane.org>
+Description:
+ Read only. Without interrupts enabled gets current position
+ values by reading. Poll "position" with interrupt conditions
+ set, to get notified; see Documentation/.../fsl,mma8653fc.txt
+
+ position file format:
+ "x y z [landscape/portrait status] [front/back status]"
+
+ x y z values:
+ in mg
+ landscape/portrait status char:
+ r landscape right
+ d portrait down
+ u portrait up
+ l landscape left
+ front/back status char:
+ f front facing
+ b back facing
+
+
+Documentation/devicetree/bindings/...
+-------------------------------------
+Required properties:
+- compatible
+ "fsl,mma8653fc"
+- reg
+ I2C address
+
+Optional properties:
+
+- interrupt-parent
+ a phandle for the interrupt controller (see
+ Documentation/devicetree/bindings/interrupt-controller/interrupts.txt)
+- interrupts
+ interrupt line to which the chip is connected (active low)
+- int1
+ set to use interrupt line 1, default is line 2
+ the interrupt sources can be routed to one of the two lines
+- ir-freefall-motion-x
+ activate freefall/motion interrupts on x axis
+- ir-freefall-motion-y
+ activate freefall/motion interrupts on y axis
+- ir-freefall-motion-z
+ activate freefall/motion interrupts on z axis
+- irq-threshold
+ 0 < value < 8000: threshold for motion interrupts in mg
+- ir-landscape-portrait
+ activate landscape/portrait interrupts
+- ir-auto-wake
+ activate wake/sleep change interrupts
+- ir-data-ready:
+ activate data-ready interrupts
+ Interrupt events can be activated in any combination.
+- dynamic-range
+ 2, 4, or 8: dynamic measurement range in g, default: 2
+ In ±2 g mode, sensitivity = 256 counts/g.
+ In ±4 g mode, sensitivity = 128 counts/g.
+ In ±8 g mode, sensitivity = 64 counts/g.
+- auto-wake-sleep
+ auto sleep mode (lower frequency)
+- motion-mode
+ use motion mode instead of freefall mode (trigger if >threshold).
+ per default an interrupt occurs if motion values fall below the
+ value set in "threshold" and therefore can detect free fall on the
+ vertical axis (depending on the position of the device).
+ Setting this values inverts the behaviour and an interrupt occurs
+ above the threshold value, so usually activate horizontal axis in
+ this case.
+
+- x-offset
+ 0 < value < 500: calibration offset in mg
+ The offset correction values are used to realign the Zero-g position
+ of the X, Y, and Z-axis after the device is mounted on a board.
+ this value has an offset of 250 itself:
+ 0 is -250mg, 250 is 0 mg, 500 is 250mg
+- y-offset
+ see x-offset
+- z-offset
+ see x-offset
+
+Example 1:
+for a device laying on flat ground to recognize acceleration over 100mg.
+x-axis is calibrated to +10mg. Adapt interrupt line to your device.
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ interrupt-parent = <&gpio1>;
+ interrupts = <5 0>;
+ reg = <0x1d>;
+
+ dynamic-range = <2>;
+ motion-mode;
+ ir-freefall-motion-x;
+ ir-freefall-motion-y;
+ irq-threshold = <100>;
+ x-offset = <160>;
+};
+
+Example 2:
+for a device mounted on a wall with y being the vertical axis. This recognizes
+y-acceleration below 800mg, so free fall or changing the orientation of the
+device (y not being the vertical axis and having ~1000mg anymore).
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ interrupt-parent = <&gpio1>;
+ interrupts = <5 0>;
+ reg = <0x1d>;
+
+ dynamic-range = <2>;
+ ir-freefall-motion-y;
+ irq-threshold = <800>;
+};
+
+Example 3:
+minimal example. lets users read current acceleration values. No polling
+is available.
+
+mma8653fc@1d {
+ compatible = "fsl,mma8653fc";
+ reg = <0x1d>;
+};
diff --git a/drivers/staging/mma8653fc/mma8653fc.c b/drivers/staging/mma8653fc/mma8653fc.c
new file mode 100644
index 0000000..4bd7f99
--- /dev/null
+++ b/drivers/staging/mma8653fc/mma8653fc.c
@@ -0,0 +1,864 @@
+/*
+ * mma8653fc.c - Support for Freescale MMA8653FC 3-axis 10-bit accelerometer
+ *
+ * Copyright (c) 2014 Theobroma Systems Design and Consulting 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.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+
+#define DRV_NAME "mma8653fc"
+#define MMA8653FC_DEVICE_ID 0x5a
+
+#define MMA8653FC_STATUS 0x00
+
+#define ZYXOW_MASK 0x80
+#define ZYXDR 0x08
+
+#define MMA8653FC_WHO_AM_I 0x0d
+
+#define MMA8653FC_SYSMOD 0x0b
+#define STATE_STANDBY 0x00
+#define STATE_WAKE 0x01
+#define STATE_SLEEP 0x02
+
+#define MMA8450_STATUS_ZXYDR 0x08
+
+/*
+ * 10 bit output data registers
+ * MSB: 7:0 bits 9:2 of data word
+ * LSB: 7:6 bits 1:0 of data word
+ */
+#define MMA8653FC_OUT_X_MSB 0x01
+#define MMA8653FC_OUT_X_LSB 0x02
+#define MMA8653FC_OUT_Y_MSB 0x03
+#define MMA8653FC_OUT_Y_LSB 0x04
+#define MMA8653FC_OUT_Z_MSB 0x05
+#define MMA8653FC_OUT_Z_LSB 0x06
+
+/*
+ * Portrait/Landscape Status
+ */
+#define MMA8653FC_PL_STATUS 0x10
+
+#define NEWLP 0x80
+#define LAPO_HIGH 0x04
+#define LAPO_LOW 0x02
+#define BAFRO 0x01
+
+/*
+ * Portrait/Landscape Configuration
+ */
+#define MMA8653FC_PL_CFG 0x11
+
+#define PL_EN (1 << 6)
+
+/*
+ * Data calibration registers
+ */
+#define MMA8653FC_OFF_X 0x2f
+#define MMA8653FC_OFF_Y 0x30
+#define MMA8653FC_OFF_Z 0x31
+
+/* 0 to 500 for dts, but -250 to 250 in mg */
+#define DEFAULT_OFF 250
+
+/*
+ * bits 1:0 dynamic range
+ * 00 +/- 2g
+ * 01 +/- 4g
+ * 10 +/- 8g
+ *
+ * HPF_Out bit 4 - data high pass or low pass filtered
+ */
+#define MMA8653FC_XYZ_DATA_CFG 0x0e
+
+#define RANGE_MASK 0x03
+#define RANGE2G 0x00
+#define RANGE4G 0x01
+#define RANGE8G 0x02
+/* values for calculation */
+#define SHIFT_2G 8
+#define INCR_2G 128
+#define SHIFT_4G 7
+#define INCR_4G 64
+#define SHIFT_8G 6
+#define INCR_8G 32
+#define DYN_RANGE_2G 2
+#define DYN_RANGE_4G 4
+#define DYN_RANGE_8G 8
+
+/*
+ * System Control Reg 1
+ */
+#define MMA8653FC_CTRL_REG1 0x2a
+
+#define ACTIVE_BIT (1 << 0)
+#define ODR_MASK 0x38
+#define ODR_DEFAULT 0x20 /* 50 Hz */
+#define ASLP_RATE_MASK 0xc0
+#define ASLP_RATE_DEFAULT 0x80 /* 6.25 Hz */
+
+/*
+ * Sys Control Reg 2
+ *
+ * auto-sleep enable, software reset
+ */
+#define MMA8653FC_CTRL_REG2 0x2b
+
+#define SLPE (1 << 2)
+#define SELFTEST (1 << 7)
+#define SOFT_RESET (1 << 6)
+
+/*
+ * Interrupt Source
+ */
+#define MMA8653FC_INT_SOURCE 0x0c
+
+#define SRC_ASLP (1 << 7)
+#define SRC_LNDPRT (1 << 4)
+#define SRC_FF_MT (1 << 2)
+#define SRC_DRDY (1 << 0)
+
+/*
+ * Interrupt Control Register
+ *
+ * default: active low
+ * default push-pull, not open-drain
+ */
+#define MMA8653FC_CTRL_REG3 0x2c
+
+#define WAKE_LNDPRT (1 << 5)
+#define WAKE_FF_MT (1 << 3)
+#define IPOL (1 << 1)
+#define PP_OD (1 << 0)
+
+/*
+ * Interrupt Enable Register
+ */
+#define MMA8653FC_CTRL_REG4 0x2d
+
+#define INT_EN_ASLP (1 << 7)
+#define INT_EN_LNDPRT (1 << 4)
+#define INT_EN_FF_MT (1 << 2)
+#define INT_EN_DRDY (1 << 0)
+
+/*
+ * Interrupt Configuration Register
+ * bit value 0 ... INT2 (default)
+ * bit value 1 ... INT1
+ */
+#define MMA8653FC_CTRL_REG5 0x2e
+
+#define INT_CFG_ASLP (1 << 7)
+#define INT_CFG_LNDPRT (1 << 4)
+#define INT_CFG_FF_MT (1 << 2)
+#define INT_CFG_DRDY (1 << 0)
+
+/*
+ * Freefall / Motion Configuration Register
+ *
+ * Event Latch enable/disable, motion or freefall mode
+ * and event flag enable per axis
+ */
+#define MMA8653FC_FF_MT_CFG 0x15
+
+#define FF_MT_CFG_ELE (1 << 7)
+#define FF_MT_CFG_OAE (1 << 6)
+#define FF_MT_CFG_ZEFE (1 << 5)
+#define FF_MT_CFG_YEFE (1 << 4)
+#define FF_MT_CFG_XEFE (1 << 3)
+
+/*
+ * Freefall / Motion Source Register
+ */
+#define MMA8653FC_FF_MT_SRC 0x16
+
+/*
+ * Freefall / Motion Threshold Register
+ *
+ * define motion threshold
+ * 0.063 g/LSB, 127 counts(0x7f) (7 bit from LSB)
+ * range: 0.063g - 8g
+ */
+#define MMA8653FC_FF_MT_THS 0x17
+
+struct axis_triple {
+ s16 x;
+ s16 y;
+ s16 z;
+};
+
+struct mma8653fc_pdata {
+ s8 x_axis_offset;
+ s8 y_axis_offset;
+ s8 z_axis_offset;
+ bool auto_wake_sleep;
+ u32 range;
+ bool int1;
+ bool motion_mode;
+ u8 freefall_motion_thr;
+ bool int_src_data_ready;
+ bool int_src_ff_mt_x;
+ bool int_src_ff_mt_y;
+ bool int_src_ff_mt_z;
+ bool int_src_lndprt;
+ bool int_src_aslp;
+};
+
+struct mma8653fc {
+ struct i2c_client *client;
+ struct mutex mutex;
+ struct mma8653fc_pdata pdata;
+ struct axis_triple saved;
+ char orientation;
+ char bafro;
+ bool standby;
+ int irq;
+ unsigned int_mask;
+ u8 (*read)(struct mma8653fc *, unsigned char);
+ void (*write)(struct mma8653fc *, unsigned char, unsigned char);
+};
+
+/* defaults */
+static const struct mma8653fc_pdata mma8653fc_default_init = {
+ .range = 2,
+ .x_axis_offset = DEFAULT_OFF,
+ .y_axis_offset = DEFAULT_OFF,
+ .z_axis_offset = DEFAULT_OFF,
+ .auto_wake_sleep = false,
+ .int1 = false,
+ .motion_mode = false,
+ .freefall_motion_thr = 5,
+ .int_src_data_ready = false,
+ .int_src_ff_mt_x = false,
+ .int_src_ff_mt_y = false,
+ .int_src_ff_mt_z = false,
+ .int_src_lndprt = false,
+ .int_src_aslp = false,
+};
+
+static void mma8653fc_get_triple(struct mma8653fc *mma)
+{
+ u8 buf[6];
+ u8 status;
+
+ buf[0] = 0;
+
+ status = mma->read(mma, MMA8653FC_STATUS);
+ if (status & ZYXOW_MASK)
+ dev_dbg(&mma->client->dev, "previous read not completed\n");
+
+ buf[0] = mma->read(mma, MMA8653FC_OUT_X_MSB);
+ buf[1] = mma->read(mma, MMA8653FC_OUT_X_LSB);
+ buf[2] = mma->read(mma, MMA8653FC_OUT_Y_MSB);
+ buf[3] = mma->read(mma, MMA8653FC_OUT_Y_LSB);
+ buf[4] = mma->read(mma, MMA8653FC_OUT_Z_MSB);
+ buf[5] = mma->read(mma, MMA8653FC_OUT_Z_LSB);
+
+ mutex_lock(&mma->mutex);
+ /* move from registers to s16 */
+ mma->saved.x = (buf[1] | (buf[0] << 8)) >> 6;
+ mma->saved.y = (buf[3] | (buf[2] << 8)) >> 6;
+ mma->saved.z = (buf[5] | (buf[4] << 8)) >> 6;
+ mma->saved.x = sign_extend32(mma->saved.x, 9);
+ mma->saved.y = sign_extend32(mma->saved.y, 9);
+ mma->saved.z = sign_extend32(mma->saved.z, 9);
+
+ /* calc g, see data sheet and application note */
+ switch (mma->pdata.range) {
+ case DYN_RANGE_2G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_2G) >> SHIFT_2G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_2G) >> SHIFT_2G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_2G) >> SHIFT_2G);
+ break;
+ case DYN_RANGE_4G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_4G) >> SHIFT_4G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_4G) >> SHIFT_4G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_4G) >> SHIFT_4G);
+ break;
+ case DYN_RANGE_8G:
+ mma->saved.x = le16_to_cpu((1000 * mma->saved.x +
+ INCR_8G) >> SHIFT_8G);
+ mma->saved.y = le16_to_cpu((1000 * mma->saved.y +
+ INCR_8G) >> SHIFT_8G);
+ mma->saved.z = le16_to_cpu((1000 * mma->saved.z +
+ INCR_8G) >> SHIFT_8G);
+ break;
+ default:
+ dev_err(&mma->client->dev, "internal data corrupt\n");
+ }
+ mutex_unlock(&mma->mutex);
+}
+
+static void mma8653fc_get_orientation(struct mma8653fc *mma, u8 byte)
+{
+ if ((byte & LAPO_HIGH) && !(LAPO_LOW))
+ mma->orientation = 'r'; /* landscape right */
+ if (!(byte & LAPO_HIGH) && (byte & LAPO_LOW))
+ mma->orientation = 'd'; /* portrait down */
+ if (!(byte & LAPO_HIGH) && !(byte & LAPO_LOW))
+ mma->orientation = 'u'; /* portrait up */
+ if ((byte & LAPO_HIGH) && (byte & LAPO_LOW))
+ mma->orientation = 'l'; /* landscape left */
+
+ if (byte & BAFRO)
+ mma->bafro = 'b'; /* back facing */
+ else
+ mma->bafro = 'f'; /* front facing */
+}
+
+static irqreturn_t mma8653fc_irq(int irq, void *handle)
+{
+ struct mma8653fc *mma = handle;
+ u8 int_src;
+ u8 byte;
+
+ int_src = mma->read(mma, MMA8653FC_INT_SOURCE);
+ if (int_src & SRC_DRDY)
+ /* data ready handle */
+ if (int_src & SRC_FF_MT) {
+ /* freefall/motion change handle */
+ dev_dbg(&mma->client->dev,
+ "freefall or motion change\n");
+ byte = mma->read(mma, MMA8653FC_FF_MT_SRC);
+ }
+ if (int_src & SRC_LNDPRT) {
+ /* landscape/portrait change handle */
+ dev_dbg(&mma->client->dev,
+ "landscape / portrait change\n");
+ byte = mma->read(mma, MMA8653FC_PL_STATUS);
+ mma8653fc_get_orientation(mma, byte);
+ }
+ if (int_src & SRC_ASLP)
+ /* autosleep change handle */
+ mma8653fc_get_triple(mma);
+
+ sysfs_notify(&mma->client->dev.kobj, NULL, "position");
+
+ return IRQ_HANDLED;
+}
+
+static void __mma8653fc_enable(struct mma8653fc *mma)
+{
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte | ACTIVE_BIT);
+}
+
+static void __mma8653fc_disable(struct mma8653fc *mma)
+{
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte & ~ACTIVE_BIT);
+}
+
+static ssize_t mma8653fc_standby_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", mma->standby);
+}
+
+static ssize_t mma8653fc_standby_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ unsigned int val;
+ int error;
+
+ error = kstrtouint(buf, 10, &val);
+ if (error)
+ return error;
+
+ mutex_lock(&mma->mutex);
+
+ if (val) {
+ if (!mma->standby)
+ __mma8653fc_disable(mma);
+ } else {
+ if (mma->standby)
+ __mma8653fc_enable(mma);
+ }
+
+ mma->standby = !!val;
+
+ mutex_unlock(&mma->mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR(standby, 0664, mma8653fc_standby_show,
+ mma8653fc_standby_store);
+
+static ssize_t mma8653fc_currentmode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ ssize_t count;
+ u8 byte;
+
+ byte = mma->read(mma, MMA8653FC_SYSMOD);
+ if (byte < 0)
+ return byte;
+
+ switch (byte) {
+ case STATE_STANDBY:
+ count = scnprintf(buf, PAGE_SIZE, "standby\n");
+ break;
+ case STATE_WAKE:
+ count = scnprintf(buf, PAGE_SIZE, "wake\n");
+ break;
+ case STATE_SLEEP:
+ count = scnprintf(buf, PAGE_SIZE, "sleep\n");
+ break;
+ default:
+ count = scnprintf(buf, PAGE_SIZE, "unknown\n");
+ }
+ return count;
+}
+static DEVICE_ATTR(currentmode, S_IRUGO, mma8653fc_currentmode_show, NULL);
+
+static ssize_t mma8653fc_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+ ssize_t count;
+ u8 byte;
+
+ if (!mma->irq) {
+ byte = mma->read(mma, MMA8653FC_PL_STATUS);
+ if (byte & NEWLP)
+ mma8653fc_get_orientation(mma, byte);
+
+ byte = mma->read(mma, MMA8653FC_STATUS);
+ if (byte & ZYXDR)
+ mma8653fc_get_triple(mma);
+
+ msleep(20);
+ dev_dbg(&client->dev, "data polled\n");
+ }
+ mutex_lock(&mma->mutex);
+ count = scnprintf(buf, PAGE_SIZE, "%d %d %d %c %c\n",
+ mma->saved.x, mma->saved.y, mma->saved.z,
+ mma->orientation, mma->bafro);
+ mutex_unlock(&mma->mutex);
+
+ return count;
+}
+static DEVICE_ATTR(position, S_IRUGO, mma8653fc_position_show, NULL);
+
+static struct attribute *mma8653fc_attributes[] = {
+ &dev_attr_position.attr,
+ &dev_attr_standby.attr,
+ &dev_attr_currentmode.attr,
+ NULL,
+};
+
+static const struct attribute_group mma8653fc_attr_group = {
+ .attrs = mma8653fc_attributes,
+};
+
+static u8 mma8653fc_read(struct mma8653fc *mma, unsigned char reg)
+{
+ u8 val;
+
+ val = i2c_smbus_read_byte_data(mma->client, reg);
+ if (val < 0) {
+ dev_err(&mma->client->dev,
+ "failed to read %x register\n", reg);
+ }
+ return val;
+}
+
+static void mma8653fc_write(struct mma8653fc *mma, unsigned char reg,
+ unsigned char val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(mma->client, reg, val);
+ if (ret) {
+ dev_err(&mma->client->dev,
+ "failed to write %x register\n", reg);
+ }
+}
+
+static const struct of_device_id mma8653fc_dt_ids[] = {
+ { .compatible = "fsl,mma8653fc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mma8653fc_dt_ids);
+
+static const struct mma8653fc_pdata *mma8653fc_probe_dt(struct device *dev)
+{
+ struct mma8653fc_pdata *pdata;
+ struct device_node *node = dev->of_node;
+ const struct of_device_id *match;
+ u32 testu32;
+ s32 tests32;
+
+ if (!node) {
+ dev_err(dev, "no associated DT data\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ match = of_match_device(mma8653fc_dt_ids, dev);
+ if (!match) {
+ dev_err(dev, "unknown device model\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ *pdata = mma8653fc_default_init;
+
+ /* overwrite from dts */
+ testu32 = pdata->x_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "x-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on X axis\n", tests32);
+ /* calc register value, resolution: 1.96mg */
+ pdata->x_axis_offset = (s8) (tests32 / 2);
+ }
+ testu32 = pdata->y_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "y-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on Y axis\n", tests32);
+ pdata->y_axis_offset = (s8) (tests32 / 2);
+ }
+ testu32 = pdata->z_axis_offset;
+ tests32 = 0;
+ of_property_read_u32(node, "z-offset", &testu32);
+ tests32 = testu32 - DEFAULT_OFF;
+ if ((tests32) && (tests32 <= DEFAULT_OFF) &&
+ (tests32 >= -DEFAULT_OFF)) {
+ dev_info(dev, "use %dmg offset on Z axis\n", tests32);
+ pdata->z_axis_offset = (s8) (tests32 / 2);
+ }
+
+ testu32 = 0;
+ of_property_read_u32(node, "dynamic-range", &testu32);
+ if ((testu32) && (testu32 != 2) && (testu32 != 4) && (testu32 != 8)) {
+ dev_warn(dev, "wrong value for full scale range in dtb\n");
+ } else {
+ if (testu32)
+ pdata->range = testu32;
+ }
+
+ if (of_property_read_bool(node, "auto-wake-sleep"))
+ pdata->auto_wake_sleep = true;
+
+ if (of_property_read_bool(node, "int1"))
+ pdata->int1 = true;
+
+ if (of_property_read_bool(node, "motion-mode"))
+ pdata->motion_mode = true;
+
+ if (of_property_read_bool(node, "ir-data-ready"))
+ pdata->int_src_data_ready = true;
+
+ if (of_property_read_bool(node, "ir-freefall-motion-x"))
+ pdata->int_src_ff_mt_x = true;
+
+ if (of_property_read_bool(node, "ir-freefall-motion-y"))
+ pdata->int_src_ff_mt_y = true;
+
+ if (of_property_read_bool(node, "ir-freefall-motion-z"))
+ pdata->int_src_ff_mt_z = true;
+
+ if (of_property_read_bool(node, "ir-auto-wake"))
+ pdata->int_src_aslp = true;
+
+ if (of_property_read_bool(node, "ir-landscape-portrait"))
+ pdata->int_src_lndprt = true;
+
+ testu32 = 0;
+ of_property_read_u32(node, "irq-threshold", &testu32);
+ /* always uses maximum range +/- 8000g, resolution 63mg */
+ if ((testu32 <= 8000) && (testu32 > 0)) {
+ dev_dbg(dev, "use freefall / motion threshold %dmg\n",
+ testu32);
+ /* calculate register value from mg */
+ pdata->freefall_motion_thr = (testu32 / 63) + 1;
+ }
+
+ return pdata;
+}
+static int mma8653fc_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mma8653fc *mma;
+ const struct mma8653fc_pdata *pdata;
+ int err;
+ u8 byte;
+
+ err = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA);
+ if (!err) {
+ dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
+ return -EIO;
+ }
+
+ mma = devm_kzalloc(&client->dev, sizeof(*mma), GFP_KERNEL);
+ if (!mma)
+ err = -ENOMEM;
+
+ pdata = dev_get_platdata(&client->dev);
+ if (!pdata) {
+ pdata = mma8653fc_probe_dt(&client->dev);
+ if (IS_ERR(pdata)) {
+ err = PTR_ERR(pdata);
+ dev_err(&client->dev, "pdata from DT failed\n");
+ return err;
+ }
+ }
+ mma->pdata = *pdata;
+ pdata = &mma->pdata;
+ mma->client = client;
+ mma->read = &mma8653fc_read;
+ mma->write = &mma8653fc_write;
+
+ mutex_init(&mma->mutex);
+
+ i2c_set_clientdata(client, mma);
+
+ err = sysfs_create_group(&client->dev.kobj, &mma8653fc_attr_group);
+ if (err)
+ return err;
+
+ byte = mma->read(mma, MMA8653FC_WHO_AM_I);
+ if (byte != MMA8653FC_DEVICE_ID) {
+ dev_err(&client->dev, "wrong device for driver\n");
+ return -ENODEV;
+ }
+ dev_info(&client->dev, "loading driver for device id %x\n", byte);
+
+ mma->irq = irq_of_parse_and_map(client->dev.of_node, 0);
+ if (!mma->irq) {
+ dev_err(&client->dev, "Unable to parse or map IRQ\n");
+ goto no_irq;
+ }
+
+ err = irq_set_irq_type(mma->irq, IRQ_TYPE_EDGE_FALLING);
+ if (err) {
+ dev_err(&client->dev, "set_irq_type failed\n");
+ return err;
+ }
+
+ err = devm_request_threaded_irq(&client->dev, mma->irq, NULL,
+ mma8653fc_irq, IRQF_ONESHOT,
+ dev_name(&mma->client->dev), mma);
+ if (err) {
+ dev_err(&client->dev, "irq %d busy?\n", mma->irq);
+ return err;
+ }
+
+ mma->write(mma, MMA8653FC_CTRL_REG2, SOFT_RESET);
+
+ __mma8653fc_disable(mma);
+ mma->standby = true;
+
+ /* enable desired interrupts */
+ mma->orientation = '\0';
+ mma->bafro = '\0';
+ byte = 0;
+ if (pdata->int_src_data_ready) {
+ byte |= INT_EN_DRDY;
+ dev_dbg(&client->dev, "DATA READY interrupt source enabled\n");
+ }
+ if (pdata->int_src_ff_mt_x || pdata->int_src_ff_mt_y ||
+ pdata->int_src_ff_mt_z) {
+ byte |= INT_EN_FF_MT;
+ dev_dbg(&client->dev, "FF MT interrupt source enabled\n");
+ }
+ if (pdata->int_src_lndprt) {
+ mma->write(mma, MMA8653FC_PL_CFG, PL_EN);
+ byte |= INT_EN_LNDPRT;
+ dev_dbg(&client->dev, "LNDPRT interrupt source enabled\n");
+ }
+ if (pdata->int_src_aslp) {
+ byte |= INT_EN_ASLP;
+ dev_dbg(&client->dev, "ASLP interrupt source enabled\n");
+ }
+ mma->write(mma, MMA8653FC_CTRL_REG4, byte);
+
+ /* force everything to line 1 */
+ if (pdata->int1) {
+ mma->write(mma, MMA8653FC_CTRL_REG5,
+ (INT_CFG_ASLP | INT_CFG_LNDPRT |
+ INT_CFG_FF_MT | INT_CFG_DRDY));
+ dev_dbg(&client->dev, "using interrupt line 1\n");
+ }
+no_irq:
+ /* range mode */
+ byte = mma->read(mma, MMA8653FC_XYZ_DATA_CFG);
+ byte &= ~RANGE_MASK;
+ switch (pdata->range) {
+ case DYN_RANGE_2G:
+ byte |= RANGE2G;
+ dev_dbg(&client->dev, "use 2g range\n");
+ break;
+ case DYN_RANGE_4G:
+ byte |= RANGE4G;
+ dev_dbg(&client->dev, "use 4g range\n");
+ break;
+ case DYN_RANGE_8G:
+ byte |= RANGE8G;
+ dev_dbg(&client->dev, "use 8g range\n");
+ break;
+ default:
+ dev_err(&client->dev, "wrong range mode value\n");
+ return -EINVAL;
+ }
+ mma->write(mma, MMA8653FC_XYZ_DATA_CFG, byte);
+
+ /* data calibration offsets */
+ if (pdata->x_axis_offset)
+ mma->write(mma, MMA8653FC_OFF_X, pdata->x_axis_offset);
+ if (pdata->y_axis_offset)
+ mma->write(mma, MMA8653FC_OFF_Y, pdata->y_axis_offset);
+ if (pdata->z_axis_offset)
+ mma->write(mma, MMA8653FC_OFF_Z, pdata->z_axis_offset);
+
+ /* if autosleep, wake on both landscape and motion changes */
+ if (pdata->auto_wake_sleep) {
+ byte = 0;
+ byte |= WAKE_LNDPRT;
+ byte |= WAKE_FF_MT;
+ mma->write(mma, MMA8653FC_CTRL_REG3, byte);
+ mma->write(mma, MMA8653FC_CTRL_REG2, SLPE);
+ dev_dbg(&client->dev, "auto sleep enabled\n");
+ }
+
+ /* data rates */
+ byte = 0;
+ byte = mma->read(mma, MMA8653FC_CTRL_REG1);
+ byte &= ~ODR_MASK;
+ byte |= ODR_DEFAULT;
+ byte &= ~ASLP_RATE_MASK;
+ byte |= ASLP_RATE_DEFAULT;
+ mma->write(mma, MMA8653FC_CTRL_REG1, byte);
+
+ /* freefall / motion config */
+ byte = 0;
+ if (pdata->motion_mode) {
+ byte |= FF_MT_CFG_OAE;
+ dev_dbg(&client->dev, "detect motion instead of freefall\n");
+ }
+ byte |= FF_MT_CFG_ELE;
+ if (pdata->int_src_ff_mt_x)
+ byte |= FF_MT_CFG_XEFE;
+ if (pdata->int_src_ff_mt_y)
+ byte |= FF_MT_CFG_YEFE;
+ if (pdata->int_src_ff_mt_z)
+ byte |= FF_MT_CFG_ZEFE;
+ mma->write(mma, MMA8653FC_FF_MT_CFG, byte);
+
+ if (pdata->freefall_motion_thr) {
+ mma->write(mma, MMA8653FC_FF_MT_THS,
+ pdata->freefall_motion_thr);
+ /* calculate back to mg */
+ dev_dbg(&client->dev, "threshold set to %dmg\n",
+ (63 * pdata->freefall_motion_thr) - 1);
+ }
+
+ return 0;
+}
+
+static int mma8653fc_remove(struct i2c_client *client)
+{
+ dev_dbg(&client->dev, "unregistered accelerometer\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mma8653fc_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ __mma8653fc_disable(mma);
+
+ return 0;
+}
+
+static int mma8653fc_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mma8653fc *mma = i2c_get_clientdata(client);
+
+ __mma8653fc_enable(mma);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mma8653fc_pm_ops, mma8653fc_suspend, mma8653fc_resume);
+#define MMA8653FC_PM_OPS (&mma8653fc_pm_ops)
+#else
+#define MMA8653FC_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id mma8653fc_id[] = {
+ { DRV_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mma8653fc_id);
+
+static struct i2c_driver mma8653fc_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = mma8653fc_dt_ids,
+ .pm = MMA8653FC_PM_OPS,
+ },
+ .probe = mma8653fc_i2c_probe,
+ .remove = mma8653fc_remove,
+ .id_table = mma8653fc_id,
+};
+
+module_i2c_driver(mma8653fc_driver);
+
+MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@theobroma-systems.com");
+MODULE_DESCRIPTION("Freescale's MMA8653FC Three-Axis Accelerometer I2C Driver");
+MODULE_LICENSE("GPL");
--
2.1.4
^ permalink raw reply related
* Re: [PATCH v9 tip 3/9] tracing: attach BPF programs to kprobes
From: Masami Hiramatsu @ 2015-03-21 12:14 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Ingo Molnar, Steven Rostedt, Namhyung Kim,
Arnaldo Carvalho de Melo, Jiri Olsa, David S. Miller,
Daniel Borkmann, Peter Zijlstra, linux-api-u79uwXL29TY76Z2rM5mHXA,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1426894210-27441-4-git-send-email-ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
(2015/03/21 8:30), Alexei Starovoitov wrote:
> User interface:
> struct perf_event_attr attr = {.type = PERF_TYPE_TRACEPOINT, .config = event_id, ...};
> event_fd = perf_event_open(&attr,...);
> ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
>
> prog_fd is a file descriptor associated with BPF program previously loaded.
> event_id is an ID of created kprobe
>
> close(event_fd) - automatically detaches BPF program from it
>
> BPF programs can call in-kernel helper functions to:
> - lookup/update/delete elements in maps
> - probe_read - wraper of probe_kernel_read() used to access any kernel
> data structures
>
> BPF programs receive 'struct pt_regs *' as an input
> ('struct pt_regs' is architecture dependent)
> and return 0 to ignore the event and 1 to store kprobe event into ring buffer.
>
> Note, kprobes are _not_ a stable kernel ABI, so bpf programs attached to
> kprobes must be recompiled for every kernel version and user must supply correct
> LINUX_VERSION_CODE in attr.kern_version during bpf_prog_load() call.
>
Would you mean that the ABI of kprobe-based BPF programs? Kprobe API/ABIs
(register_kprobe() etc.) are stable, but the code who use kprobes certainly
depends the kernel binary by design. So, if you meant it, BPF programs must
be recompiled for every kernel binaries (including configuration changes,
not only its version).
Thank you,
> Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
> Reviewed-by: Steven Rostedt <rostedt-nx8X9YLhiw1AfugRpC6u6w@public.gmane.org>
> ---
> include/linux/ftrace_event.h | 11 ++++
> include/uapi/linux/bpf.h | 3 +
> include/uapi/linux/perf_event.h | 1 +
> kernel/bpf/syscall.c | 7 ++-
> kernel/events/core.c | 59 ++++++++++++++++++
> kernel/trace/Makefile | 1 +
> kernel/trace/bpf_trace.c | 130 +++++++++++++++++++++++++++++++++++++++
> kernel/trace/trace_kprobe.c | 8 +++
> 8 files changed, 219 insertions(+), 1 deletion(-)
> create mode 100644 kernel/trace/bpf_trace.c
>
> diff --git a/include/linux/ftrace_event.h b/include/linux/ftrace_event.h
> index 77325e1a1816..0aa535bc9f05 100644
> --- a/include/linux/ftrace_event.h
> +++ b/include/linux/ftrace_event.h
> @@ -13,6 +13,7 @@ struct trace_array;
> struct trace_buffer;
> struct tracer;
> struct dentry;
> +struct bpf_prog;
>
> struct trace_print_flags {
> unsigned long mask;
> @@ -306,6 +307,7 @@ struct ftrace_event_call {
> #ifdef CONFIG_PERF_EVENTS
> int perf_refcount;
> struct hlist_head __percpu *perf_events;
> + struct bpf_prog *prog;
>
> int (*perf_perm)(struct ftrace_event_call *,
> struct perf_event *);
> @@ -551,6 +553,15 @@ event_trigger_unlock_commit_regs(struct ftrace_event_file *file,
> event_triggers_post_call(file, tt);
> }
>
> +#ifdef CONFIG_BPF_SYSCALL
> +unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx);
> +#else
> +static inline unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx)
> +{
> + return 1;
> +}
> +#endif
> +
> enum {
> FILTER_OTHER = 0,
> FILTER_STATIC_STRING,
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index 45da7ec7d274..b2948feeb70b 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -118,6 +118,7 @@ enum bpf_map_type {
> enum bpf_prog_type {
> BPF_PROG_TYPE_UNSPEC,
> BPF_PROG_TYPE_SOCKET_FILTER,
> + BPF_PROG_TYPE_KPROBE,
> };
>
> /* flags for BPF_MAP_UPDATE_ELEM command */
> @@ -151,6 +152,7 @@ union bpf_attr {
> __u32 log_level; /* verbosity level of verifier */
> __u32 log_size; /* size of user buffer */
> __aligned_u64 log_buf; /* user supplied buffer */
> + __u32 kern_version; /* checked when prog_type=kprobe */
> };
> } __attribute__((aligned(8)));
>
> @@ -162,6 +164,7 @@ enum bpf_func_id {
> BPF_FUNC_map_lookup_elem, /* void *map_lookup_elem(&map, &key) */
> BPF_FUNC_map_update_elem, /* int map_update_elem(&map, &key, &value, flags) */
> BPF_FUNC_map_delete_elem, /* int map_delete_elem(&map, &key) */
> + BPF_FUNC_probe_read, /* int bpf_probe_read(void *dst, int size, void *src) */
> __BPF_FUNC_MAX_ID,
> };
>
> diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
> index 3c8b45de57ec..ad4dade2a502 100644
> --- a/include/uapi/linux/perf_event.h
> +++ b/include/uapi/linux/perf_event.h
> @@ -382,6 +382,7 @@ struct perf_event_attr {
> #define PERF_EVENT_IOC_SET_OUTPUT _IO ('$', 5)
> #define PERF_EVENT_IOC_SET_FILTER _IOW('$', 6, char *)
> #define PERF_EVENT_IOC_ID _IOR('$', 7, __u64 *)
> +#define PERF_EVENT_IOC_SET_BPF _IOW('$', 8, __u32)
>
> enum perf_event_ioc_flags {
> PERF_IOC_FLAG_GROUP = 1U << 0,
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 536edc2be307..504c10b990ef 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -16,6 +16,7 @@
> #include <linux/file.h>
> #include <linux/license.h>
> #include <linux/filter.h>
> +#include <linux/version.h>
>
> static LIST_HEAD(bpf_map_types);
>
> @@ -467,7 +468,7 @@ struct bpf_prog *bpf_prog_get(u32 ufd)
> }
>
> /* last field in 'union bpf_attr' used by this command */
> -#define BPF_PROG_LOAD_LAST_FIELD log_buf
> +#define BPF_PROG_LOAD_LAST_FIELD kern_version
>
> static int bpf_prog_load(union bpf_attr *attr)
> {
> @@ -492,6 +493,10 @@ static int bpf_prog_load(union bpf_attr *attr)
> if (attr->insn_cnt >= BPF_MAXINSNS)
> return -EINVAL;
>
> + if (type == BPF_PROG_TYPE_KPROBE &&
> + attr->kern_version != LINUX_VERSION_CODE)
> + return -EINVAL;
> +
> /* plain bpf_prog allocation */
> prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);
> if (!prog)
> diff --git a/kernel/events/core.c b/kernel/events/core.c
> index 2709063eb26b..3a45e7f6b2df 100644
> --- a/kernel/events/core.c
> +++ b/kernel/events/core.c
> @@ -42,6 +42,8 @@
> #include <linux/module.h>
> #include <linux/mman.h>
> #include <linux/compat.h>
> +#include <linux/bpf.h>
> +#include <linux/filter.h>
>
> #include "internal.h"
>
> @@ -3402,6 +3404,7 @@ errout:
> }
>
> static void perf_event_free_filter(struct perf_event *event);
> +static void perf_event_free_bpf_prog(struct perf_event *event);
>
> static void free_event_rcu(struct rcu_head *head)
> {
> @@ -3411,6 +3414,7 @@ static void free_event_rcu(struct rcu_head *head)
> if (event->ns)
> put_pid_ns(event->ns);
> perf_event_free_filter(event);
> + perf_event_free_bpf_prog(event);
> kfree(event);
> }
>
> @@ -3923,6 +3927,7 @@ static inline int perf_fget_light(int fd, struct fd *p)
> static int perf_event_set_output(struct perf_event *event,
> struct perf_event *output_event);
> static int perf_event_set_filter(struct perf_event *event, void __user *arg);
> +static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd);
>
> static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned long arg)
> {
> @@ -3976,6 +3981,9 @@ static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned lon
> case PERF_EVENT_IOC_SET_FILTER:
> return perf_event_set_filter(event, (void __user *)arg);
>
> + case PERF_EVENT_IOC_SET_BPF:
> + return perf_event_set_bpf_prog(event, arg);
> +
> default:
> return -ENOTTY;
> }
> @@ -6436,6 +6444,49 @@ static void perf_event_free_filter(struct perf_event *event)
> ftrace_profile_free_filter(event);
> }
>
> +static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
> +{
> + struct bpf_prog *prog;
> +
> + if (event->attr.type != PERF_TYPE_TRACEPOINT)
> + return -EINVAL;
> +
> + if (event->tp_event->prog)
> + return -EEXIST;
> +
> + if (!(event->tp_event->flags & TRACE_EVENT_FL_KPROBE))
> + /* bpf programs can only be attached to kprobes */
> + return -EINVAL;
> +
> + prog = bpf_prog_get(prog_fd);
> + if (IS_ERR(prog))
> + return PTR_ERR(prog);
> +
> + if (prog->aux->prog_type != BPF_PROG_TYPE_KPROBE) {
> + /* valid fd, but invalid bpf program type */
> + bpf_prog_put(prog);
> + return -EINVAL;
> + }
> +
> + event->tp_event->prog = prog;
> +
> + return 0;
> +}
> +
> +static void perf_event_free_bpf_prog(struct perf_event *event)
> +{
> + struct bpf_prog *prog;
> +
> + if (!event->tp_event)
> + return;
> +
> + prog = event->tp_event->prog;
> + if (prog) {
> + event->tp_event->prog = NULL;
> + bpf_prog_put(prog);
> + }
> +}
> +
> #else
>
> static inline void perf_tp_register(void)
> @@ -6451,6 +6502,14 @@ static void perf_event_free_filter(struct perf_event *event)
> {
> }
>
> +static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
> +{
> + return -ENOENT;
> +}
> +
> +static void perf_event_free_bpf_prog(struct perf_event *event)
> +{
> +}
> #endif /* CONFIG_EVENT_TRACING */
>
> #ifdef CONFIG_HAVE_HW_BREAKPOINT
> diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
> index 98f26588255e..c575a300103b 100644
> --- a/kernel/trace/Makefile
> +++ b/kernel/trace/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o
> endif
> obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
> obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
> +obj-$(CONFIG_BPF_SYSCALL) += bpf_trace.o
> obj-$(CONFIG_KPROBE_EVENT) += trace_kprobe.o
> obj-$(CONFIG_TRACEPOINTS) += power-traces.o
> ifeq ($(CONFIG_PM),y)
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> new file mode 100644
> index 000000000000..10ac48da4bb7
> --- /dev/null
> +++ b/kernel/trace/bpf_trace.c
> @@ -0,0 +1,130 @@
> +/* Copyright (c) 2011-2015 PLUMgrid, http://plumgrid.com
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of version 2 of the GNU General Public
> + * License as published by the Free Software Foundation.
> + */
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include <linux/bpf.h>
> +#include <linux/filter.h>
> +#include <linux/uaccess.h>
> +#include "trace.h"
> +
> +static DEFINE_PER_CPU(int, bpf_prog_active);
> +
> +/**
> + * trace_call_bpf - invoke BPF program
> + * @prog: BPF program
> + * @ctx: opaque context pointer
> + *
> + * kprobe handlers execute BPF programs via this helper.
> + * Can be used from static tracepoints in the future.
> + *
> + * Return: BPF programs always return an integer which is interpreted by
> + * kprobe handler as:
> + * 0 - return from kprobe (event is filtered out)
> + * 1 - store kprobe event into ring buffer
> + * Other values are reserved and currently alias to 1
> + */
> +unsigned int trace_call_bpf(struct bpf_prog *prog, void *ctx)
> +{
> + unsigned int ret;
> +
> + if (in_nmi()) /* not supported yet */
> + return 1;
> +
> + preempt_disable();
> +
> + if (unlikely(__this_cpu_inc_return(bpf_prog_active) != 1)) {
> + /*
> + * since some bpf program is already running on this cpu,
> + * don't call into another bpf program (same or different)
> + * and don't send kprobe event into ring-buffer,
> + * so return zero here
> + */
> + ret = 0;
> + goto out;
> + }
> +
> + rcu_read_lock();
> + ret = BPF_PROG_RUN(prog, ctx);
> + rcu_read_unlock();
> +
> + out:
> + __this_cpu_dec(bpf_prog_active);
> + preempt_enable();
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(trace_call_bpf);
> +
> +static u64 bpf_probe_read(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
> +{
> + void *dst = (void *) (long) r1;
> + int size = (int) r2;
> + void *unsafe_ptr = (void *) (long) r3;
> +
> + return probe_kernel_read(dst, unsafe_ptr, size);
> +}
> +
> +static const struct bpf_func_proto bpf_probe_read_proto = {
> + .func = bpf_probe_read,
> + .gpl_only = true,
> + .ret_type = RET_INTEGER,
> + .arg1_type = ARG_PTR_TO_STACK,
> + .arg2_type = ARG_CONST_STACK_SIZE,
> + .arg3_type = ARG_ANYTHING,
> +};
> +
> +static const struct bpf_func_proto *kprobe_prog_func_proto(enum bpf_func_id func_id)
> +{
> + switch (func_id) {
> + case BPF_FUNC_map_lookup_elem:
> + return &bpf_map_lookup_elem_proto;
> + case BPF_FUNC_map_update_elem:
> + return &bpf_map_update_elem_proto;
> + case BPF_FUNC_map_delete_elem:
> + return &bpf_map_delete_elem_proto;
> + case BPF_FUNC_probe_read:
> + return &bpf_probe_read_proto;
> + default:
> + return NULL;
> + }
> +}
> +
> +/* bpf+kprobe programs can access fields of 'struct pt_regs' */
> +static bool kprobe_prog_is_valid_access(int off, int size, enum bpf_access_type type)
> +{
> + /* check bounds */
> + if (off < 0 || off >= sizeof(struct pt_regs))
> + return false;
> +
> + /* only read is allowed */
> + if (type != BPF_READ)
> + return false;
> +
> + /* disallow misaligned access */
> + if (off % size != 0)
> + return false;
> +
> + return true;
> +}
> +
> +static struct bpf_verifier_ops kprobe_prog_ops = {
> + .get_func_proto = kprobe_prog_func_proto,
> + .is_valid_access = kprobe_prog_is_valid_access,
> +};
> +
> +static struct bpf_prog_type_list kprobe_tl = {
> + .ops = &kprobe_prog_ops,
> + .type = BPF_PROG_TYPE_KPROBE,
> +};
> +
> +static int __init register_kprobe_prog_ops(void)
> +{
> + bpf_register_prog_type(&kprobe_tl);
> + return 0;
> +}
> +late_initcall(register_kprobe_prog_ops);
> diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
> index 8fa549f6f528..dc3462507d7c 100644
> --- a/kernel/trace/trace_kprobe.c
> +++ b/kernel/trace/trace_kprobe.c
> @@ -1134,11 +1134,15 @@ static void
> kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
> {
> struct ftrace_event_call *call = &tk->tp.call;
> + struct bpf_prog *prog = call->prog;
> struct kprobe_trace_entry_head *entry;
> struct hlist_head *head;
> int size, __size, dsize;
> int rctx;
>
> + if (prog && !trace_call_bpf(prog, regs))
> + return;
> +
> head = this_cpu_ptr(call->perf_events);
> if (hlist_empty(head))
> return;
> @@ -1165,11 +1169,15 @@ kretprobe_perf_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
> struct pt_regs *regs)
> {
> struct ftrace_event_call *call = &tk->tp.call;
> + struct bpf_prog *prog = call->prog;
> struct kretprobe_trace_entry_head *entry;
> struct hlist_head *head;
> int size, __size, dsize;
> int rctx;
>
> + if (prog && !trace_call_bpf(prog, regs))
> + return;
> +
> head = this_cpu_ptr(call->perf_events);
> if (hlist_empty(head))
> return;
>
--
Masami HIRAMATSU
Software Platform Research Dept. Linux Technology Research Center
Hitachi, Ltd., Yokohama Research Laboratory
E-mail: masami.hiramatsu.pt-FCd8Q96Dh0JBDgjK7y7TUQ@public.gmane.org
^ permalink raw reply
* Re: [PATCH v9 tip 2/9] tracing: add kprobe flag
From: Masami Hiramatsu @ 2015-03-21 12:16 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Ingo Molnar, Steven Rostedt, Namhyung Kim,
Arnaldo Carvalho de Melo, Jiri Olsa, David S. Miller,
Daniel Borkmann, Peter Zijlstra, linux-api-u79uwXL29TY76Z2rM5mHXA,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1426894210-27441-3-git-send-email-ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
(2015/03/21 8:30), Alexei Starovoitov wrote:
> add TRACE_EVENT_FL_KPROBE flag to differentiate kprobe type of tracepoints,
> since bpf programs can only be attached to kprobe type of
> PERF_TYPE_TRACEPOINT perf events.
>
Looks ok to me.
Reviewed-by: <masami.hiramatsu.pt-FCd8Q96Dh0JBDgjK7y7TUQ@public.gmane.org>
Thank you!
> Signed-off-by: Alexei Starovoitov <ast-uqk4Ao+rVK5Wk0Htik3J/w@public.gmane.org>
> Reviewed-by: Steven Rostedt <rostedt-nx8X9YLhiw1AfugRpC6u6w@public.gmane.org>
> ---
> include/linux/ftrace_event.h | 3 +++
> kernel/trace/trace_kprobe.c | 2 +-
> 2 files changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/ftrace_event.h b/include/linux/ftrace_event.h
> index c674ee8f7fca..77325e1a1816 100644
> --- a/include/linux/ftrace_event.h
> +++ b/include/linux/ftrace_event.h
> @@ -252,6 +252,7 @@ enum {
> TRACE_EVENT_FL_WAS_ENABLED_BIT,
> TRACE_EVENT_FL_USE_CALL_FILTER_BIT,
> TRACE_EVENT_FL_TRACEPOINT_BIT,
> + TRACE_EVENT_FL_KPROBE_BIT,
> };
>
> /*
> @@ -265,6 +266,7 @@ enum {
> * it is best to clear the buffers that used it).
> * USE_CALL_FILTER - For ftrace internal events, don't use file filter
> * TRACEPOINT - Event is a tracepoint
> + * KPROBE - Event is a kprobe
> */
> enum {
> TRACE_EVENT_FL_FILTERED = (1 << TRACE_EVENT_FL_FILTERED_BIT),
> @@ -274,6 +276,7 @@ enum {
> TRACE_EVENT_FL_WAS_ENABLED = (1 << TRACE_EVENT_FL_WAS_ENABLED_BIT),
> TRACE_EVENT_FL_USE_CALL_FILTER = (1 << TRACE_EVENT_FL_USE_CALL_FILTER_BIT),
> TRACE_EVENT_FL_TRACEPOINT = (1 << TRACE_EVENT_FL_TRACEPOINT_BIT),
> + TRACE_EVENT_FL_KPROBE = (1 << TRACE_EVENT_FL_KPROBE_BIT),
> };
>
> struct ftrace_event_call {
> diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
> index d73f565b4e06..8fa549f6f528 100644
> --- a/kernel/trace/trace_kprobe.c
> +++ b/kernel/trace/trace_kprobe.c
> @@ -1286,7 +1286,7 @@ static int register_kprobe_event(struct trace_kprobe *tk)
> kfree(call->print_fmt);
> return -ENODEV;
> }
> - call->flags = 0;
> + call->flags = TRACE_EVENT_FL_KPROBE;
> call->class->reg = kprobe_register;
> call->data = tk;
> ret = trace_add_event_call(call);
>
--
Masami HIRAMATSU
Software Platform Research Dept. Linux Technology Research Center
Hitachi, Ltd., Yokohama Research Laboratory
E-mail: masami.hiramatsu.pt-FCd8Q96Dh0JBDgjK7y7TUQ@public.gmane.org
^ permalink raw reply
* Re: [PATCH v9 tip 1/9] bpf: make internal bpf API independent of CONFIG_BPF_SYSCALL ifdefs
From: Masami Hiramatsu @ 2015-03-21 12:16 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Ingo Molnar, Steven Rostedt, Namhyung Kim,
Arnaldo Carvalho de Melo, Jiri Olsa, David S. Miller,
Daniel Borkmann, Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <1426894210-27441-2-git-send-email-ast@plumgrid.com>
(2015/03/21 8:30), Alexei Starovoitov wrote:
> From: Daniel Borkmann <daniel@iogearbox.net>
>
> Socket filter code and other subsystems with upcoming eBPF support should
> not need to deal with the fact that we have CONFIG_BPF_SYSCALL defined or
> not.
>
> Having the bpf syscall as a config option is a nice thing and I'd expect
> it to stay that way for expert users (I presume one day the default setting
> of it might change, though), but code making use of it should not care if
> it's actually enabled or not.
>
> Instead, hide this via header files and let the rest deal with it.
>
Looks good to me.
Reviewed-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Thank you,
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
> ---
> include/linux/bpf.h | 20 ++++++++++++++++----
> 1 file changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index bbfceb756452..c2e21113ecc0 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -113,8 +113,6 @@ struct bpf_prog_type_list {
> enum bpf_prog_type type;
> };
>
> -void bpf_register_prog_type(struct bpf_prog_type_list *tl);
> -
> struct bpf_prog;
>
> struct bpf_prog_aux {
> @@ -129,11 +127,25 @@ struct bpf_prog_aux {
> };
>
> #ifdef CONFIG_BPF_SYSCALL
> +void bpf_register_prog_type(struct bpf_prog_type_list *tl);
> +
> void bpf_prog_put(struct bpf_prog *prog);
> +struct bpf_prog *bpf_prog_get(u32 ufd);
> #else
> -static inline void bpf_prog_put(struct bpf_prog *prog) {}
> +static inline void bpf_register_prog_type(struct bpf_prog_type_list *tl)
> +{
> +}
> +
> +static inline struct bpf_prog *bpf_prog_get(u32 ufd)
> +{
> + return ERR_PTR(-EOPNOTSUPP);
> +}
> +
> +static inline void bpf_prog_put(struct bpf_prog *prog)
> +{
> +}
> #endif
> -struct bpf_prog *bpf_prog_get(u32 ufd);
> +
> /* verify correctness of eBPF program */
> int bpf_check(struct bpf_prog *fp, union bpf_attr *attr);
>
>
--
Masami HIRAMATSU
Software Platform Research Dept. Linux Technology Research Center
Hitachi, Ltd., Yokohama Research Laboratory
E-mail: masami.hiramatsu.pt@hitachi.com
^ permalink raw reply
* Re: [PATCH 05/45] drm.h: include stdlib.h in userspace
From: Mikko Rapeli @ 2015-03-21 12:17 UTC (permalink / raw)
To: Emil Velikov
Cc: David Howells, Daniel Vetter, Dave Airlie,
Linux-Kernel@Vger. Kernel. Org, ML dri-devel,
linux-api-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <CACvgo50PA5nb27EcxQqXY49C5hmvDDxi3WOqB=op-wx_WEGd+Q-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
On Fri, Mar 20, 2015 at 08:25:40PM +0000, Emil Velikov wrote:
> On 23 February 2015 at 10:35, Mikko Rapeli <mikko.rapeli-X3B1VOXEql0@public.gmane.org> wrote:
> > On Mon, Feb 23, 2015 at 10:26:58AM +0000, Emil Velikov wrote:
> >> On 16/02/15 23:05, Mikko Rapeli wrote:
> >> > Fixes <drm/drm.h> compilation error:
> >> >
> >> > drm/drm.h:132:2: error: unknown type name ‘size_t’
> >> >
> >> Hi Mikko,
> >>
> >> Can you let us know how you're getting these (series-wise) errors ? I've
> >> been meaning to sync the uapi/drm and libdrm headers and would be nice
> >> to have an extra step to test things.
> >
> > This should have everything needed to reproduce these compile errors,
> > though some of the errors hide behind other errors and fixes:
> >
> > https://lkml.org/lkml/2015/2/16/525
> >
> Thanks for the link Mikko.
>
> Afaict the general consensus seems to be that one should avoid using
> stdint's uint8_t, but stick to __u8 and friends. Did you had the
> chance to roll out another series that does so ?
Yes, new series with these changes is on the way. I'm trying to follow up to
all other review comments as well and get down to 100% compiling uapi
headers; 35 failures to go...
> That aside I'm not 100% sure that doing the UAPI split, as is, was the
> perfect solution. Afaik drm used to live as an out of tree userspace
> library(libdrm). Not sure at which point the major restructuring took
> part, but one is certain - libdrm remains the only authoritative
> sources of the headers. It's possible that some buggy programs pull
> the UAPI headers while linking against the library, but I'd say that
> won't end up well in the long term. Additionally since the UAPI split
> the `make update-headers' target used to sync libdrm's headers have
> been broken leading people to copy misc. hunks and/or files. Leading
> to greater chance of things going sour.
>
> All that said, I will need to gather some opinions for drm developers
> and maintainers if the idea of part revering 718dcedd7e8(UAPI:
> (Scripted) Disintegrate include/drm) will be the way forward.
Ok, I'll follow what is available in Linus' tree (or -next, not shure which
one I should track for these changes).
-Mikko
^ permalink raw reply
* Re: [PATCH V7] Allow compaction of unevictable pages
From: Peter Zijlstra @ 2015-03-21 13:33 UTC (permalink / raw)
To: Andrew Morton
Cc: Eric B Munson, Vlastimil Babka, Thomas Gleixner,
Christoph Lameter, Mel Gorman, David Rientjes, Rik van Riel,
Michal Hocko, linux-doc, linux-rt-users, linux-mm, linux-api,
linux-kernel
In-Reply-To: <20150320151703.dfd116931ddb397ade8bfd8c@linux-foundation.org>
On Fri, Mar 20, 2015 at 03:17:03PM -0700, Andrew Morton wrote:
> On Fri, 20 Mar 2015 09:49:50 -0400 Eric B Munson <emunson@akamai.com> wrote:
>
> > Currently, pages which are marked as unevictable are protected from
> > compaction, but not from other types of migration. The POSIX real time
> > extension explicitly states that mlock() will prevent a major page
> > fault, but the spirit of this is that mlock() should give a process the
> > ability to control sources of latency, including minor page faults.
> > However, the mlock manpage only explicitly says that a locked page will
> > not be written to swap and this can cause some confusion. The
> > compaction code today does not give a developer who wants to avoid swap
> > but wants to have large contiguous areas available any method to achieve
> > this state. This patch introduces a sysctl for controlling compaction
> > behavior with respect to the unevictable lru. Users that demand no page
> > faults after a page is present can set compact_unevictable_allowed to 0
> > and users who need the large contiguous areas can enable compaction on
> > locked memory by leaving the default value of 1.
>
> Do we really really really need the /proc knob? We're already
> migrating these pages so users of mlock will occasionally see some
> latency - how likely is it that this patch will significantly damage
> anyone?
-rt disables everything that causes those migrations (with exception of
sys_migrate_pages).
And like I've argued before; mlock() is part of the posix real-time
extensions and the real-time people really do not want those migrations.
And while the letter of the posix spec allows migrations, the spirit
clearly does not.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply
* Re: [PATCH v9 tip 3/9] tracing: attach BPF programs to kprobes
From: Alexei Starovoitov @ 2015-03-21 16:02 UTC (permalink / raw)
To: Masami Hiramatsu
Cc: Ingo Molnar, Steven Rostedt, Namhyung Kim,
Arnaldo Carvalho de Melo, Jiri Olsa, David S. Miller,
Daniel Borkmann, Peter Zijlstra, linux-api, netdev, linux-kernel
In-Reply-To: <550D60C2.8010502@hitachi.com>
On 3/21/15 5:14 AM, Masami Hiramatsu wrote:
> (2015/03/21 8:30), Alexei Starovoitov wrote:
>>
>> Note, kprobes are _not_ a stable kernel ABI, so bpf programs attached to
>> kprobes must be recompiled for every kernel version and user must supply correct
>> LINUX_VERSION_CODE in attr.kern_version during bpf_prog_load() call.
>>
>
> Would you mean that the ABI of kprobe-based BPF programs? Kprobe API/ABIs
> (register_kprobe() etc.) are stable, but the code who use kprobes certainly
> depends the kernel binary by design. So, if you meant it, BPF programs must
> be recompiled for every kernel binaries (including configuration changes,
> not only its version).
yes. I mainly meant that bpf+kprobe programs must be recompiled
for every kernel binary.
But you're incorrect saying that register_kprobe API is stable.
It's equally kernel dependent.
register_kprobe(struct kprobe *p) is export_gpl, but it takes
kernel internal 'struct kprobe' and it's not declared in uapi header.
Prototype of kprobe_handler_t is also kernel internal, so whoever
is using kprobes must recompile their code every time.
If we want, we can change register_kprobe function name to something
else. Just like kernel modules cannot expect that exported symbols
will stay around from version to version. We don't care when we
break out of tree modules.
^ permalink raw reply
* [PATCHv3 0/2] N900 Modem Speech Support
From: Sebastian Reichel @ 2015-03-21 19:09 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Peter Ujfalusi, Kai Vehmanen, Pavel Machek, Pali Rohar,
Aaro Koskinen, Ivaylo Dimitrov, linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA
Hi,
This patchset contains the missing speech data support for the
Nokia N900 modem.
Userland access goes via /dev/cmt_speech. The API is implemented in
libcmtspeechdata, which is used by ofono and the freesmartphone.org project.
Apart from that the device is also used by the phone binaries distributed
with Maemo. So while this is a new userland ABI for the mainline kernel it
has been tested in the wild for some years.
Simple Testing of the API can be done by checking out libcmtspeechdata [0],
building the test tool and executing it. The tool will loop back audio data
received from the caller.
I have prepared a kernel branch for this patchset, which can be found at [1].
Changes since PATCHv2 [3]:
* Change MODULE_LICENSE from "GPL" to "GPL v2"
* Add some cleanups suggested by Pavel
* Add missing dependency on SSI_PROTOCOL in Kconfig
Changes since PATCHv1 [2]:
* Squash cmt-speech patches together
* cs_alloc_cmds(): GFP_ATOMIC -> GFP_KERNEL
* CS_SET_WAKELINE ioctl: Add sanity check
[0] https://lkml.org/lkml/2015/2/11/526
[1] git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-hsi.git branch/cmt-speech-2
[2] https://lkml.org/lkml/2015/3/1/282
[3] https://lkml.org/lkml/2015/3/4/1139
-- Sebastian
Kai Vehmanen (1):
HSI: cmt_speech: Add cmt-speech driver
Sebastian Reichel (1):
HSI: nokia-modem: Add cmt-speech support
drivers/hsi/clients/Kconfig | 12 +-
drivers/hsi/clients/Makefile | 1 +
drivers/hsi/clients/cmt_speech.c | 1456 ++++++++++++++++++++++++++++++++++
drivers/hsi/clients/nokia-modem.c | 32 +-
include/uapi/linux/hsi/Kbuild | 2 +-
include/uapi/linux/hsi/cs-protocol.h | 113 +++
6 files changed, 1613 insertions(+), 3 deletions(-)
create mode 100644 drivers/hsi/clients/cmt_speech.c
create mode 100644 include/uapi/linux/hsi/cs-protocol.h
--
2.1.4
^ permalink raw reply
* [PATCHv3 1/2] HSI: cmt_speech: Add cmt-speech driver
From: Sebastian Reichel @ 2015-03-21 19:09 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Peter Ujfalusi, Kai Vehmanen, Pavel Machek, Pali Rohar,
Aaro Koskinen, Ivaylo Dimitrov, linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-api-u79uwXL29TY76Z2rM5mHXA, Kai Vehmanen
In-Reply-To: <1426964957-5023-1-git-send-email-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
From: Kai Vehmanen <kai.vehmanen-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
Introduces the cmt-speech driver, which implements
a character device interface for transferring speech
data frames over HSI/SSI.
The driver is used to exchange voice/speech data between
the Nokia N900/N950/N9's modem and its cpu.
Signed-off-by: Kai Vehmanen <kai.vehmanen-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
Signed-off-by: Carlos Chinea <carlos.chinea-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
Signed-off-by: Joni Lapilainen <joni.lapilainen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Since the original driver has been written for 2.6.28 some
build fixes and general cleanups have been added by me:
* fix build for 4.0 kernel
* replace GFP_ATOMIC with GFP_KERNEL in cs_alloc_cmds()
* add sanity check for CS_SET_WAKELINE ioctl
* cleanup driver initialisation
* rename driver to cmt-speech to be consistent with
ssi-protocol driver
* move cs-protocol.h to include/uapi/linux/hsi, since
it describes a userspace API
* replace hardcoded channels numbers with values provided
via the HSI framework (e.g. coming from DT)
Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
drivers/hsi/clients/Kconfig | 10 +
drivers/hsi/clients/Makefile | 1 +
drivers/hsi/clients/cmt_speech.c | 1456 ++++++++++++++++++++++++++++++++++
include/uapi/linux/hsi/Kbuild | 2 +-
include/uapi/linux/hsi/cs-protocol.h | 113 +++
5 files changed, 1581 insertions(+), 1 deletion(-)
create mode 100644 drivers/hsi/clients/cmt_speech.c
create mode 100644 include/uapi/linux/hsi/cs-protocol.h
diff --git a/drivers/hsi/clients/Kconfig b/drivers/hsi/clients/Kconfig
index bc60dec..86c8495 100644
--- a/drivers/hsi/clients/Kconfig
+++ b/drivers/hsi/clients/Kconfig
@@ -13,6 +13,16 @@ config NOKIA_MODEM
If unsure, say N.
+config CMT_SPEECH
+ tristate "CMT speech"
+ depends on HSI && SSI_PROTOCOL
+ help
+ If you say Y here, you will enable the CMT speech protocol used
+ by Nokia modems. If you say M the protocol will be available as
+ module named cmt_speech.
+
+ If unsure, say N.
+
config SSI_PROTOCOL
tristate "SSI protocol"
depends on HSI && PHONET && OMAP_SSI
diff --git a/drivers/hsi/clients/Makefile b/drivers/hsi/clients/Makefile
index 4d5bc0e..2607232 100644
--- a/drivers/hsi/clients/Makefile
+++ b/drivers/hsi/clients/Makefile
@@ -4,4 +4,5 @@
obj-$(CONFIG_NOKIA_MODEM) += nokia-modem.o
obj-$(CONFIG_SSI_PROTOCOL) += ssi_protocol.o
+obj-$(CONFIG_CMT_SPEECH) += cmt_speech.o
obj-$(CONFIG_HSI_CHAR) += hsi_char.o
diff --git a/drivers/hsi/clients/cmt_speech.c b/drivers/hsi/clients/cmt_speech.c
new file mode 100644
index 0000000..e9560ef
--- /dev/null
+++ b/drivers/hsi/clients/cmt_speech.c
@@ -0,0 +1,1456 @@
+/*
+ * cmt_speech.c - HSI CMT speech driver
+ *
+ * Copyright (C) 2008,2009,2010 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Kai Vehmanen <kai.vehmanen-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
+ * Original author: Peter Ujfalusi <peter.ujfalusi-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/pm_qos.h>
+#include <linux/hsi/hsi.h>
+#include <linux/hsi/ssi_protocol.h>
+#include <linux/hsi/cs-protocol.h>
+
+#define CS_MMAP_SIZE PAGE_SIZE
+
+struct char_queue {
+ struct list_head list;
+ u32 msg;
+};
+
+struct cs_char {
+ unsigned int opened;
+ struct hsi_client *cl;
+ struct cs_hsi_iface *hi;
+ struct list_head chardev_queue;
+ struct list_head dataind_queue;
+ int dataind_pending;
+ /* mmap things */
+ unsigned long mmap_base;
+ unsigned long mmap_size;
+ spinlock_t lock;
+ struct fasync_struct *async_queue;
+ wait_queue_head_t wait;
+ /* hsi channel ids */
+ int channel_id_cmd;
+ int channel_id_data;
+};
+
+#define SSI_CHANNEL_STATE_READING 1
+#define SSI_CHANNEL_STATE_WRITING (1 << 1)
+#define SSI_CHANNEL_STATE_POLL (1 << 2)
+#define SSI_CHANNEL_STATE_ERROR (1 << 3)
+
+#define TARGET_MASK 0xf000000
+#define TARGET_REMOTE (1 << CS_DOMAIN_SHIFT)
+#define TARGET_LOCAL 0
+
+/* Number of pre-allocated commands buffers */
+#define CS_MAX_CMDS 4
+
+/*
+ * During data transfers, transactions must be handled
+ * within 20ms (fixed value in cmtspeech HSI protocol)
+ */
+#define CS_QOS_LATENCY_FOR_DATA_USEC 20000
+
+/* Timeout to wait for pending HSI transfers to complete */
+#define CS_HSI_TRANSFER_TIMEOUT_MS 500
+
+
+#define RX_PTR_BOUNDARY_SHIFT 8
+#define RX_PTR_MAX_SHIFT (RX_PTR_BOUNDARY_SHIFT + \
+ CS_MAX_BUFFERS_SHIFT)
+struct cs_hsi_iface {
+ struct hsi_client *cl;
+ struct hsi_client *master;
+
+ unsigned int iface_state;
+ unsigned int wakeline_state;
+ unsigned int control_state;
+ unsigned int data_state;
+
+ /* state exposed to application */
+ struct cs_mmap_config_block *mmap_cfg;
+
+ unsigned long mmap_base;
+ unsigned long mmap_size;
+
+ unsigned int rx_slot;
+ unsigned int tx_slot;
+
+ /* note: for security reasons, we do not trust the contents of
+ * mmap_cfg, but instead duplicate the variables here */
+ unsigned int buf_size;
+ unsigned int rx_bufs;
+ unsigned int tx_bufs;
+ unsigned int rx_ptr_boundary;
+ unsigned int rx_offsets[CS_MAX_BUFFERS];
+ unsigned int tx_offsets[CS_MAX_BUFFERS];
+
+ /* size of aligned memory blocks */
+ unsigned int slot_size;
+ unsigned int flags;
+
+ struct list_head cmdqueue;
+
+ struct hsi_msg *data_rx_msg;
+ struct hsi_msg *data_tx_msg;
+ wait_queue_head_t datawait;
+
+ struct pm_qos_request pm_qos_req;
+
+ spinlock_t lock;
+};
+
+static struct cs_char cs_char_data;
+
+static void cs_hsi_read_on_control(struct cs_hsi_iface *hi);
+static void cs_hsi_read_on_data(struct cs_hsi_iface *hi);
+
+static inline void rx_ptr_shift_too_big(void)
+{
+ BUILD_BUG_ON((1LLU << RX_PTR_MAX_SHIFT) > UINT_MAX);
+}
+
+static void cs_notify(u32 message, struct list_head *head)
+{
+ struct char_queue *entry;
+
+ spin_lock(&cs_char_data.lock);
+
+ if (!cs_char_data.opened) {
+ spin_unlock(&cs_char_data.lock);
+ goto out;
+ }
+
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ if (!entry) {
+ dev_err(&cs_char_data.cl->device,
+ "Can't allocate new entry for the queue.\n");
+ spin_unlock(&cs_char_data.lock);
+ goto out;
+ }
+
+ entry->msg = message;
+ list_add_tail(&entry->list, head);
+
+ spin_unlock(&cs_char_data.lock);
+
+ wake_up_interruptible(&cs_char_data.wait);
+ kill_fasync(&cs_char_data.async_queue, SIGIO, POLL_IN);
+
+out:
+ return;
+}
+
+static u32 cs_pop_entry(struct list_head *head)
+{
+ struct char_queue *entry;
+ u32 data;
+
+ entry = list_entry(head->next, struct char_queue, list);
+ data = entry->msg;
+ list_del(&entry->list);
+ kfree(entry);
+
+ return data;
+}
+
+static void cs_notify_control(u32 message)
+{
+ cs_notify(message, &cs_char_data.chardev_queue);
+}
+
+static void cs_notify_data(u32 message, int maxlength)
+{
+ cs_notify(message, &cs_char_data.dataind_queue);
+
+ spin_lock(&cs_char_data.lock);
+ cs_char_data.dataind_pending++;
+ while (cs_char_data.dataind_pending > maxlength &&
+ !list_empty(&cs_char_data.dataind_queue)) {
+ dev_dbg(&cs_char_data.cl->device, "data notification "
+ "queue overrun (%u entries)\n", cs_char_data.dataind_pending);
+
+ cs_pop_entry(&cs_char_data.dataind_queue);
+ cs_char_data.dataind_pending--;
+ }
+ spin_unlock(&cs_char_data.lock);
+}
+
+static inline void cs_set_cmd(struct hsi_msg *msg, u32 cmd)
+{
+ u32 *data = sg_virt(msg->sgt.sgl);
+ *data = cmd;
+}
+
+static inline u32 cs_get_cmd(struct hsi_msg *msg)
+{
+ u32 *data = sg_virt(msg->sgt.sgl);
+ return *data;
+}
+
+static void cs_release_cmd(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+
+ list_add_tail(&msg->link, &hi->cmdqueue);
+}
+
+static void cs_cmd_destructor(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+
+ spin_lock(&hi->lock);
+
+ dev_dbg(&cs_char_data.cl->device, "control cmd destructor\n");
+
+ if (hi->iface_state != CS_STATE_CLOSED)
+ dev_err(&hi->cl->device, "Cmd flushed while driver active\n");
+
+ if (msg->ttype == HSI_MSG_READ)
+ hi->control_state &=
+ ~(SSI_CHANNEL_STATE_POLL | SSI_CHANNEL_STATE_READING);
+ else if (msg->ttype == HSI_MSG_WRITE &&
+ hi->control_state & SSI_CHANNEL_STATE_WRITING)
+ hi->control_state &= ~SSI_CHANNEL_STATE_WRITING;
+
+ cs_release_cmd(msg);
+
+ spin_unlock(&hi->lock);
+}
+
+static struct hsi_msg *cs_claim_cmd(struct cs_hsi_iface* ssi)
+{
+ struct hsi_msg *msg;
+
+ BUG_ON(list_empty(&ssi->cmdqueue));
+
+ msg = list_first_entry(&ssi->cmdqueue, struct hsi_msg, link);
+ list_del(&msg->link);
+ msg->destructor = cs_cmd_destructor;
+
+ return msg;
+}
+
+static void cs_free_cmds(struct cs_hsi_iface *ssi)
+{
+ struct hsi_msg *msg, *tmp;
+
+ list_for_each_entry_safe(msg, tmp, &ssi->cmdqueue, link) {
+ list_del(&msg->link);
+ msg->destructor = NULL;
+ kfree(sg_virt(msg->sgt.sgl));
+ hsi_free_msg(msg);
+ }
+}
+
+static int cs_alloc_cmds(struct cs_hsi_iface *hi)
+{
+ struct hsi_msg *msg;
+ u32 *buf;
+ unsigned int i;
+
+ INIT_LIST_HEAD(&hi->cmdqueue);
+
+ for (i = 0; i < CS_MAX_CMDS; i++) {
+ msg = hsi_alloc_msg(1, GFP_KERNEL);
+ if (!msg)
+ goto out;
+ buf = kmalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf) {
+ hsi_free_msg(msg);
+ goto out;
+ }
+ sg_init_one(msg->sgt.sgl, buf, sizeof(*buf));
+ msg->channel = cs_char_data.channel_id_cmd;
+ msg->context = hi;
+ list_add_tail(&msg->link, &hi->cmdqueue);
+ }
+
+ return 0;
+
+out:
+ cs_free_cmds(hi);
+ return -ENOMEM;
+}
+
+static void cs_hsi_data_destructor(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+ const char *dir = (msg->ttype == HSI_MSG_READ) ? "TX" : "RX";
+
+ dev_dbg(&cs_char_data.cl->device, "Freeing data %s message\n", dir);
+
+ spin_lock(&hi->lock);
+ if (hi->iface_state != CS_STATE_CLOSED)
+ dev_err(&cs_char_data.cl->device,
+ "Data %s flush while device active\n", dir);
+ if (msg->ttype == HSI_MSG_READ)
+ hi->data_state &=
+ ~(SSI_CHANNEL_STATE_POLL | SSI_CHANNEL_STATE_READING);
+ else
+ hi->data_state &= ~SSI_CHANNEL_STATE_WRITING;
+
+ msg->status = HSI_STATUS_COMPLETED;
+ if (unlikely(waitqueue_active(&hi->datawait)))
+ wake_up_interruptible(&hi->datawait);
+
+ spin_unlock(&hi->lock);
+}
+
+static int cs_hsi_alloc_data(struct cs_hsi_iface *hi)
+{
+ struct hsi_msg *txmsg, *rxmsg;
+ int res = 0;
+
+ rxmsg = hsi_alloc_msg(1, GFP_KERNEL);
+ if (!rxmsg) {
+ res = -ENOMEM;
+ goto out1;
+ }
+ rxmsg->channel = cs_char_data.channel_id_data;
+ rxmsg->destructor = cs_hsi_data_destructor;
+ rxmsg->context = hi;
+
+ txmsg = hsi_alloc_msg(1, GFP_KERNEL);
+ if (!txmsg) {
+ res = -ENOMEM;
+ goto out2;
+ }
+ txmsg->channel = cs_char_data.channel_id_data;
+ txmsg->destructor = cs_hsi_data_destructor;
+ txmsg->context = hi;
+
+ hi->data_rx_msg = rxmsg;
+ hi->data_tx_msg = txmsg;
+
+ return 0;
+
+out2:
+ hsi_free_msg(rxmsg);
+out1:
+ return res;
+}
+
+static void cs_hsi_free_data_msg(struct hsi_msg *msg)
+{
+ WARN_ON(msg->status != HSI_STATUS_COMPLETED &&
+ msg->status != HSI_STATUS_ERROR);
+ hsi_free_msg(msg);
+}
+
+static void cs_hsi_free_data(struct cs_hsi_iface *hi)
+{
+ cs_hsi_free_data_msg(hi->data_rx_msg);
+ cs_hsi_free_data_msg(hi->data_tx_msg);
+}
+
+static inline void __cs_hsi_error_pre(struct cs_hsi_iface *hi,
+ struct hsi_msg *msg, const char *info,
+ unsigned int *state)
+{
+ spin_lock(&hi->lock);
+ dev_err(&hi->cl->device, "HSI %s error, msg %d, state %u\n",
+ info, msg->status, *state);
+}
+
+static inline void __cs_hsi_error_post(struct cs_hsi_iface *hi)
+{
+ spin_unlock(&hi->lock);
+}
+
+static inline void __cs_hsi_error_read_bits(unsigned int *state)
+{
+ *state |= SSI_CHANNEL_STATE_ERROR;
+ *state &= ~(SSI_CHANNEL_STATE_READING | SSI_CHANNEL_STATE_POLL);
+}
+
+static inline void __cs_hsi_error_write_bits(unsigned int *state)
+{
+ *state |= SSI_CHANNEL_STATE_ERROR;
+ *state &= ~SSI_CHANNEL_STATE_WRITING;
+}
+
+static void cs_hsi_control_read_error(struct cs_hsi_iface *hi,
+ struct hsi_msg *msg)
+{
+ __cs_hsi_error_pre(hi, msg, "control read", &hi->control_state);
+ cs_release_cmd(msg);
+ __cs_hsi_error_read_bits(&hi->control_state);
+ __cs_hsi_error_post(hi);
+}
+
+static void cs_hsi_control_write_error(struct cs_hsi_iface *hi,
+ struct hsi_msg *msg)
+{
+ __cs_hsi_error_pre(hi, msg, "control write", &hi->control_state);
+ cs_release_cmd(msg);
+ __cs_hsi_error_write_bits(&hi->control_state);
+ __cs_hsi_error_post(hi);
+
+}
+
+static void cs_hsi_data_read_error(struct cs_hsi_iface *hi, struct hsi_msg *msg)
+{
+ __cs_hsi_error_pre(hi, msg, "data read", &hi->data_state);
+ __cs_hsi_error_read_bits(&hi->data_state);
+ __cs_hsi_error_post(hi);
+}
+
+static void cs_hsi_data_write_error(struct cs_hsi_iface *hi,
+ struct hsi_msg *msg)
+{
+ __cs_hsi_error_pre(hi, msg, "data write", &hi->data_state);
+ __cs_hsi_error_write_bits(&hi->data_state);
+ __cs_hsi_error_post(hi);
+}
+
+static void cs_hsi_read_on_control_complete(struct hsi_msg *msg)
+{
+ u32 cmd = cs_get_cmd(msg);
+ struct cs_hsi_iface *hi = msg->context;
+
+ spin_lock(&hi->lock);
+ hi->control_state &= ~SSI_CHANNEL_STATE_READING;
+ if (msg->status == HSI_STATUS_ERROR) {
+ dev_err(&hi->cl->device, "Control RX error detected\n");
+ cs_hsi_control_read_error(hi, msg);
+ spin_unlock(&hi->lock);
+ goto out;
+ }
+ dev_dbg(&hi->cl->device, "Read on control: %08X\n", cmd);
+ cs_release_cmd(msg);
+ if (hi->flags & CS_FEAT_TSTAMP_RX_CTRL) {
+ struct timespec *tstamp =
+ &hi->mmap_cfg->tstamp_rx_ctrl;
+ do_posix_clock_monotonic_gettime(tstamp);
+ }
+ spin_unlock(&hi->lock);
+
+ cs_notify_control(cmd);
+
+out:
+ cs_hsi_read_on_control(hi);
+}
+
+static void cs_hsi_peek_on_control_complete(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+ int ret;
+
+ if (msg->status == HSI_STATUS_ERROR) {
+ dev_err(&hi->cl->device, "Control peek RX error detected\n");
+ cs_hsi_control_read_error(hi, msg);
+ return;
+ }
+
+ WARN_ON(!(hi->control_state & SSI_CHANNEL_STATE_READING));
+
+ dev_dbg(&hi->cl->device, "Peek on control complete, reading\n");
+ msg->sgt.nents = 1;
+ msg->complete = cs_hsi_read_on_control_complete;
+ ret = hsi_async_read(hi->cl, msg);
+ if (ret)
+ cs_hsi_control_read_error(hi, msg);
+}
+
+static void cs_hsi_read_on_control(struct cs_hsi_iface *hi)
+{
+ struct hsi_msg *msg;
+ int ret;
+
+ spin_lock(&hi->lock);
+ if (hi->control_state & SSI_CHANNEL_STATE_READING) {
+ dev_err(&hi->cl->device, "Control read already pending (%d)\n",
+ hi->control_state);
+ spin_unlock(&hi->lock);
+ return;
+ }
+ if (hi->control_state & SSI_CHANNEL_STATE_ERROR) {
+ dev_err(&hi->cl->device, "Control read error (%d)\n",
+ hi->control_state);
+ spin_unlock(&hi->lock);
+ return;
+ }
+ hi->control_state |= SSI_CHANNEL_STATE_READING;
+ dev_dbg(&hi->cl->device, "Issuing RX on control\n");
+ msg = cs_claim_cmd(hi);
+ spin_unlock(&hi->lock);
+
+ msg->sgt.nents = 0;
+ msg->complete = cs_hsi_peek_on_control_complete;
+ ret = hsi_async_read(hi->cl, msg);
+ if (ret)
+ cs_hsi_control_read_error(hi, msg);
+}
+
+static void cs_hsi_write_on_control_complete(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+ if (msg->status == HSI_STATUS_COMPLETED) {
+ spin_lock(&hi->lock);
+ hi->control_state &= ~SSI_CHANNEL_STATE_WRITING;
+ cs_release_cmd(msg);
+ spin_unlock(&hi->lock);
+ } else if (msg->status == HSI_STATUS_ERROR) {
+ cs_hsi_control_write_error(hi, msg);
+ } else {
+ dev_err(&hi->cl->device,
+ "unexpected status in control write callback %d\n",
+ msg->status);
+ }
+}
+
+static int cs_hsi_write_on_control(struct cs_hsi_iface *hi, u32 message)
+{
+ struct hsi_msg *msg;
+ int ret;
+
+ spin_lock(&hi->lock);
+ if (hi->control_state & SSI_CHANNEL_STATE_ERROR) {
+ spin_unlock(&hi->lock);
+ return -EIO;
+ }
+ if (hi->control_state & SSI_CHANNEL_STATE_WRITING) {
+ dev_err(&hi->cl->device,
+ "Write still pending on control channel.\n");
+ spin_unlock(&hi->lock);
+ return -EBUSY;
+ }
+ hi->control_state |= SSI_CHANNEL_STATE_WRITING;
+ msg = cs_claim_cmd(hi);
+ spin_unlock(&hi->lock);
+
+ cs_set_cmd(msg, message);
+ msg->sgt.nents = 1;
+ msg->complete = cs_hsi_write_on_control_complete;
+ dev_dbg(&hi->cl->device,
+ "Sending control message %08X\n", message);
+ ret = hsi_async_write(hi->cl, msg);
+ if (ret) {
+ dev_err(&hi->cl->device,
+ "async_write failed with %d\n", ret);
+ cs_hsi_control_write_error(hi, msg);
+ }
+
+ /*
+ * Make sure control read is always pending when issuing
+ * new control writes. This is needed as the controller
+ * may flush our messages if e.g. the peer device reboots
+ * unexpectedly (and we cannot directly resubmit a new read from
+ * the message destructor; see cs_cmd_destructor()).
+ */
+ if (!(hi->control_state & SSI_CHANNEL_STATE_READING)) {
+ dev_err(&hi->cl->device, "Restarting control reads\n");
+ cs_hsi_read_on_control(hi);
+ }
+
+ return 0;
+}
+
+static void cs_hsi_read_on_data_complete(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+ u32 payload;
+
+ if (unlikely(msg->status == HSI_STATUS_ERROR)) {
+ cs_hsi_data_read_error(hi, msg);
+ return;
+ }
+
+ spin_lock(&hi->lock);
+ WARN_ON(!(hi->data_state & SSI_CHANNEL_STATE_READING));
+ hi->data_state &= ~SSI_CHANNEL_STATE_READING;
+ payload = CS_RX_DATA_RECEIVED;
+ payload |= hi->rx_slot;
+ hi->rx_slot++;
+ hi->rx_slot %= hi->rx_ptr_boundary;
+ /* expose current rx ptr in mmap area */
+ hi->mmap_cfg->rx_ptr = hi->rx_slot;
+ if (unlikely(waitqueue_active(&hi->datawait)))
+ wake_up_interruptible(&hi->datawait);
+ spin_unlock(&hi->lock);
+
+ cs_notify_data(payload, hi->rx_bufs);
+ cs_hsi_read_on_data(hi);
+}
+
+static void cs_hsi_peek_on_data_complete(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+ u32 *address;
+ int ret;
+
+ if (unlikely(msg->status == HSI_STATUS_ERROR)) {
+ cs_hsi_data_read_error(hi, msg);
+ return;
+ }
+ if (unlikely(hi->iface_state != CS_STATE_CONFIGURED)) {
+ dev_err(&hi->cl->device, "Data received in invalid state\n");
+ cs_hsi_data_read_error(hi, msg);
+ return;
+ }
+
+ spin_lock(&hi->lock);
+ WARN_ON(!(hi->data_state & SSI_CHANNEL_STATE_POLL));
+ hi->data_state &= ~SSI_CHANNEL_STATE_POLL;
+ hi->data_state |= SSI_CHANNEL_STATE_READING;
+ spin_unlock(&hi->lock);
+
+ address = (u32 *)(hi->mmap_base +
+ hi->rx_offsets[hi->rx_slot % hi->rx_bufs]);
+ sg_init_one(msg->sgt.sgl, address, hi->buf_size);
+ msg->sgt.nents = 1;
+ msg->complete = cs_hsi_read_on_data_complete;
+ ret = hsi_async_read(hi->cl, msg);
+ if (ret)
+ cs_hsi_data_read_error(hi, msg);
+}
+
+/*
+ * Read/write transaction is ongoing. Returns false if in
+ * SSI_CHANNEL_STATE_POLL state.
+ */
+static inline int cs_state_xfer_active(unsigned int state)
+{
+ return (state & SSI_CHANNEL_STATE_WRITING) ||
+ (state & SSI_CHANNEL_STATE_READING);
+}
+
+/*
+ * No pending read/writes
+ */
+static inline int cs_state_idle(unsigned int state)
+{
+ return !(state & ~SSI_CHANNEL_STATE_ERROR);
+}
+
+static void cs_hsi_read_on_data(struct cs_hsi_iface *hi)
+{
+ struct hsi_msg *rxmsg;
+ int ret;
+
+ spin_lock(&hi->lock);
+ if (hi->data_state &
+ (SSI_CHANNEL_STATE_READING | SSI_CHANNEL_STATE_POLL)) {
+ dev_dbg(&hi->cl->device, "Data read already pending (%u)\n",
+ hi->data_state);
+ spin_unlock(&hi->lock);
+ return;
+ }
+ hi->data_state |= SSI_CHANNEL_STATE_POLL;
+ spin_unlock(&hi->lock);
+
+ rxmsg = hi->data_rx_msg;
+ sg_init_one(rxmsg->sgt.sgl, (void *)hi->mmap_base, 0);
+ rxmsg->sgt.nents = 0;
+ rxmsg->complete = cs_hsi_peek_on_data_complete;
+
+ ret = hsi_async_read(hi->cl, rxmsg);
+ if (ret)
+ cs_hsi_data_read_error(hi, rxmsg);
+}
+
+static void cs_hsi_write_on_data_complete(struct hsi_msg *msg)
+{
+ struct cs_hsi_iface *hi = msg->context;
+
+ if (msg->status == HSI_STATUS_COMPLETED) {
+ spin_lock(&hi->lock);
+ hi->data_state &= ~SSI_CHANNEL_STATE_WRITING;
+ if (unlikely(waitqueue_active(&hi->datawait)))
+ wake_up_interruptible(&hi->datawait);
+ spin_unlock(&hi->lock);
+ } else {
+ cs_hsi_data_write_error(hi, msg);
+ }
+}
+
+static int cs_hsi_write_on_data(struct cs_hsi_iface *hi, unsigned int slot)
+{
+ u32 *address;
+ struct hsi_msg *txmsg;
+ int ret;
+
+ spin_lock(&hi->lock);
+ if (hi->iface_state != CS_STATE_CONFIGURED) {
+ dev_err(&hi->cl->device, "Not configured, aborting\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ if (hi->data_state & SSI_CHANNEL_STATE_ERROR) {
+ dev_err(&hi->cl->device, "HSI error, aborting\n");
+ ret = -EIO;
+ goto error;
+ }
+ if (hi->data_state & SSI_CHANNEL_STATE_WRITING) {
+ dev_err(&hi->cl->device, "Write pending on data channel.\n");
+ ret = -EBUSY;
+ goto error;
+ }
+ hi->data_state |= SSI_CHANNEL_STATE_WRITING;
+ spin_unlock(&hi->lock);
+
+ hi->tx_slot = slot;
+ address = (u32 *)(hi->mmap_base + hi->tx_offsets[hi->tx_slot]);
+ txmsg = hi->data_tx_msg;
+ sg_init_one(txmsg->sgt.sgl, address, hi->buf_size);
+ txmsg->complete = cs_hsi_write_on_data_complete;
+ ret = hsi_async_write(hi->cl, txmsg);
+ if (ret)
+ cs_hsi_data_write_error(hi, txmsg);
+
+ return ret;
+
+error:
+ spin_unlock(&hi->lock);
+ if (ret == -EIO)
+ cs_hsi_data_write_error(hi, hi->data_tx_msg);
+
+ return ret;
+}
+
+static unsigned int cs_hsi_get_state(struct cs_hsi_iface *hi)
+{
+ return hi->iface_state;
+}
+
+static int cs_hsi_command(struct cs_hsi_iface *hi, u32 cmd)
+{
+ int ret = 0;
+
+ local_bh_disable();
+ switch (cmd & TARGET_MASK) {
+ case TARGET_REMOTE:
+ ret = cs_hsi_write_on_control(hi, cmd);
+ break;
+ case TARGET_LOCAL:
+ if ((cmd & CS_CMD_MASK) == CS_TX_DATA_READY)
+ ret = cs_hsi_write_on_data(hi, cmd & CS_PARAM_MASK);
+ else
+ ret = -EINVAL;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ local_bh_enable();
+
+ return ret;
+}
+
+static void cs_hsi_set_wakeline(struct cs_hsi_iface *hi, bool new_state)
+{
+ int change = 0;
+
+ spin_lock_bh(&hi->lock);
+ if (hi->wakeline_state != new_state) {
+ hi->wakeline_state = new_state;
+ change = 1;
+ dev_dbg(&hi->cl->device, "setting wake line to %d (%p)\n",
+ new_state, hi->cl);
+ }
+ spin_unlock_bh(&hi->lock);
+
+ if (change) {
+ if (new_state)
+ ssip_slave_start_tx(hi->master);
+ else
+ ssip_slave_stop_tx(hi->master);
+ }
+
+ dev_dbg(&hi->cl->device, "wake line set to %d (%p)\n",
+ new_state, hi->cl);
+}
+
+static void set_buffer_sizes(struct cs_hsi_iface *hi, int rx_bufs, int tx_bufs)
+{
+ hi->rx_bufs = rx_bufs;
+ hi->tx_bufs = tx_bufs;
+ hi->mmap_cfg->rx_bufs = rx_bufs;
+ hi->mmap_cfg->tx_bufs = tx_bufs;
+
+ if (hi->flags & CS_FEAT_ROLLING_RX_COUNTER) {
+ /*
+ * For more robust overrun detection, let the rx
+ * pointer run in range 0..'boundary-1'. Boundary
+ * is a multiple of rx_bufs, and limited in max size
+ * by RX_PTR_MAX_SHIFT to allow for fast ptr-diff
+ * calculation.
+ */
+ hi->rx_ptr_boundary = (rx_bufs << RX_PTR_BOUNDARY_SHIFT);
+ hi->mmap_cfg->rx_ptr_boundary = hi->rx_ptr_boundary;
+ } else {
+ hi->rx_ptr_boundary = hi->rx_bufs;
+ }
+}
+
+static int check_buf_params(struct cs_hsi_iface *hi,
+ const struct cs_buffer_config *buf_cfg)
+{
+ size_t buf_size_aligned = L1_CACHE_ALIGN(buf_cfg->buf_size) *
+ (buf_cfg->rx_bufs + buf_cfg->tx_bufs);
+ size_t ctrl_size_aligned = L1_CACHE_ALIGN(sizeof(*hi->mmap_cfg));
+ int r = 0;
+
+ if (buf_cfg->rx_bufs > CS_MAX_BUFFERS ||
+ buf_cfg->tx_bufs > CS_MAX_BUFFERS) {
+ r = -EINVAL;
+ } else if ((buf_size_aligned + ctrl_size_aligned) >= hi->mmap_size) {
+ dev_err(&hi->cl->device, "No space for the requested buffer "
+ "configuration\n");
+ r = -ENOBUFS;
+ }
+
+ return r;
+}
+
+/**
+ * Block until pending data transfers have completed.
+ */
+static int cs_hsi_data_sync(struct cs_hsi_iface *hi)
+{
+ int r = 0;
+
+ spin_lock_bh(&hi->lock);
+
+ if (!cs_state_xfer_active(hi->data_state)) {
+ dev_dbg(&hi->cl->device, "hsi_data_sync break, idle\n");
+ goto out;
+ }
+
+ for (;;) {
+ int s;
+ DEFINE_WAIT(wait);
+ if (!cs_state_xfer_active(hi->data_state))
+ goto out;
+ if (signal_pending(current)) {
+ r = -ERESTARTSYS;
+ goto out;
+ }
+ /**
+ * prepare_to_wait must be called with hi->lock held
+ * so that callbacks can check for waitqueue_active()
+ */
+ prepare_to_wait(&hi->datawait, &wait, TASK_INTERRUPTIBLE);
+ spin_unlock_bh(&hi->lock);
+ s = schedule_timeout(
+ msecs_to_jiffies(CS_HSI_TRANSFER_TIMEOUT_MS));
+ spin_lock_bh(&hi->lock);
+ finish_wait(&hi->datawait, &wait);
+ if (!s) {
+ dev_dbg(&hi->cl->device,
+ "hsi_data_sync timeout after %d ms\n",
+ CS_HSI_TRANSFER_TIMEOUT_MS);
+ r = -EIO;
+ goto out;
+ }
+ }
+
+out:
+ spin_unlock_bh(&hi->lock);
+ dev_dbg(&hi->cl->device, "hsi_data_sync done with res %d\n", r);
+
+ return r;
+}
+
+static void cs_hsi_data_enable(struct cs_hsi_iface *hi,
+ struct cs_buffer_config *buf_cfg)
+{
+ unsigned int data_start, i;
+
+ BUG_ON(hi->buf_size == 0);
+
+ set_buffer_sizes(hi, buf_cfg->rx_bufs, buf_cfg->tx_bufs);
+
+ hi->slot_size = L1_CACHE_ALIGN(hi->buf_size);
+ dev_dbg(&hi->cl->device,
+ "setting slot size to %u, buf size %u, align %u\n",
+ hi->slot_size, hi->buf_size, L1_CACHE_BYTES);
+
+ data_start = L1_CACHE_ALIGN(sizeof(*hi->mmap_cfg));
+ dev_dbg(&hi->cl->device,
+ "setting data start at %u, cfg block %u, align %u\n",
+ data_start, sizeof(*hi->mmap_cfg), L1_CACHE_BYTES);
+
+ for (i = 0; i < hi->mmap_cfg->rx_bufs; i++) {
+ hi->rx_offsets[i] = data_start + i * hi->slot_size;
+ hi->mmap_cfg->rx_offsets[i] = hi->rx_offsets[i];
+ dev_dbg(&hi->cl->device, "DL buf #%u at %u\n",
+ i, hi->rx_offsets[i]);
+ }
+ for (i = 0; i < hi->mmap_cfg->tx_bufs; i++) {
+ hi->tx_offsets[i] = data_start +
+ (i + hi->mmap_cfg->rx_bufs) * hi->slot_size;
+ hi->mmap_cfg->tx_offsets[i] = hi->tx_offsets[i];
+ dev_dbg(&hi->cl->device, "UL buf #%u at %u\n",
+ i, hi->rx_offsets[i]);
+ }
+
+ hi->iface_state = CS_STATE_CONFIGURED;
+}
+
+static void cs_hsi_data_disable(struct cs_hsi_iface *hi, int old_state)
+{
+ if (old_state == CS_STATE_CONFIGURED) {
+ dev_dbg(&hi->cl->device,
+ "closing data channel with slot size 0\n");
+ hi->iface_state = CS_STATE_OPENED;
+ }
+}
+
+static int cs_hsi_buf_config(struct cs_hsi_iface *hi,
+ struct cs_buffer_config *buf_cfg)
+{
+ int r = 0;
+ unsigned int old_state = hi->iface_state;
+
+ spin_lock_bh(&hi->lock);
+ /* Prevent new transactions during buffer reconfig */
+ if (old_state == CS_STATE_CONFIGURED)
+ hi->iface_state = CS_STATE_OPENED;
+ spin_unlock_bh(&hi->lock);
+
+ /*
+ * make sure that no non-zero data reads are ongoing before
+ * proceeding to change the buffer layout
+ */
+ r = cs_hsi_data_sync(hi);
+ if (r < 0)
+ return r;
+
+ WARN_ON(cs_state_xfer_active(hi->data_state));
+
+ spin_lock_bh(&hi->lock);
+ r = check_buf_params(hi, buf_cfg);
+ if (r < 0)
+ goto error;
+
+ hi->buf_size = buf_cfg->buf_size;
+ hi->mmap_cfg->buf_size = hi->buf_size;
+ hi->flags = buf_cfg->flags;
+
+ hi->rx_slot = 0;
+ hi->tx_slot = 0;
+ hi->slot_size = 0;
+
+ if (hi->buf_size)
+ cs_hsi_data_enable(hi, buf_cfg);
+ else
+ cs_hsi_data_disable(hi, old_state);
+
+ spin_unlock_bh(&hi->lock);
+
+ if (old_state != hi->iface_state) {
+ if (hi->iface_state == CS_STATE_CONFIGURED) {
+ pm_qos_add_request(&hi->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY,
+ CS_QOS_LATENCY_FOR_DATA_USEC);
+ local_bh_disable();
+ cs_hsi_read_on_data(hi);
+ local_bh_enable();
+ } else if (old_state == CS_STATE_CONFIGURED) {
+ pm_qos_remove_request(&hi->pm_qos_req);
+ }
+ }
+ return r;
+
+error:
+ spin_unlock_bh(&hi->lock);
+ return r;
+}
+
+static int cs_hsi_start(struct cs_hsi_iface **hi, struct hsi_client *cl,
+ unsigned long mmap_base, unsigned long mmap_size)
+{
+ int err = 0;
+ struct cs_hsi_iface *hsi_if = kzalloc(sizeof(*hsi_if), GFP_KERNEL);
+
+ dev_dbg(&cl->device, "cs_hsi_start\n");
+
+ if (!hsi_if) {
+ err = -ENOMEM;
+ goto leave0;
+ }
+ spin_lock_init(&hsi_if->lock);
+ hsi_if->cl = cl;
+ hsi_if->iface_state = CS_STATE_CLOSED;
+ hsi_if->mmap_cfg = (struct cs_mmap_config_block *)mmap_base;
+ hsi_if->mmap_base = mmap_base;
+ hsi_if->mmap_size = mmap_size;
+ memset(hsi_if->mmap_cfg, 0, sizeof(*hsi_if->mmap_cfg));
+ init_waitqueue_head(&hsi_if->datawait);
+ err = cs_alloc_cmds(hsi_if);
+ if (err < 0) {
+ dev_err(&cl->device, "Unable to alloc HSI messages\n");
+ goto leave1;
+ }
+ err = cs_hsi_alloc_data(hsi_if);
+ if (err < 0) {
+ dev_err(&cl->device, "Unable to alloc HSI messages for data\n");
+ goto leave2;
+ }
+ err = hsi_claim_port(cl, 1);
+ if (err < 0) {
+ dev_err(&cl->device,
+ "Could not open, HSI port already claimed\n");
+ goto leave3;
+ }
+ hsi_if->master = ssip_slave_get_master(cl);
+ if (IS_ERR(hsi_if->master)) {
+ dev_err(&cl->device, "Could not get HSI master client\n");
+ goto leave4;
+ }
+ if (!ssip_slave_running(hsi_if->master)) {
+ err = -ENODEV;
+ dev_err(&cl->device,
+ "HSI port not initialized\n");
+ goto leave4;
+ }
+
+ hsi_if->iface_state = CS_STATE_OPENED;
+ local_bh_disable();
+ cs_hsi_read_on_control(hsi_if);
+ local_bh_enable();
+
+ dev_dbg(&cl->device, "cs_hsi_start...done\n");
+
+ BUG_ON(!hi);
+ *hi = hsi_if;
+
+ return 0;
+
+leave4:
+ hsi_release_port(cl);
+leave3:
+ cs_hsi_free_data(hsi_if);
+leave2:
+ cs_free_cmds(hsi_if);
+leave1:
+ kfree(hsi_if);
+leave0:
+ dev_dbg(&cl->device, "cs_hsi_start...done/error\n\n");
+
+ return err;
+}
+
+static void cs_hsi_stop(struct cs_hsi_iface *hi)
+{
+ dev_dbg(&hi->cl->device, "cs_hsi_stop\n");
+ cs_hsi_set_wakeline(hi, 0);
+ ssip_slave_put_master(hi->master);
+
+ /* hsi_release_port() needs to be called with CS_STATE_CLOSED */
+ hi->iface_state = CS_STATE_CLOSED;
+ hsi_release_port(hi->cl);
+
+ /*
+ * hsi_release_port() should flush out all the pending
+ * messages, so cs_state_idle() should be true for both
+ * control and data channels.
+ */
+ WARN_ON(!cs_state_idle(hi->control_state));
+ WARN_ON(!cs_state_idle(hi->data_state));
+
+ if (pm_qos_request_active(&hi->pm_qos_req))
+ pm_qos_remove_request(&hi->pm_qos_req);
+
+ spin_lock_bh(&hi->lock);
+ cs_hsi_free_data(hi);
+ cs_free_cmds(hi);
+ spin_unlock_bh(&hi->lock);
+ kfree(hi);
+}
+
+static int cs_char_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct cs_char *csdata = vma->vm_private_data;
+ struct page *page;
+
+ page = virt_to_page(csdata->mmap_base);
+ get_page(page);
+ vmf->page = page;
+
+ return 0;
+}
+
+static struct vm_operations_struct cs_char_vm_ops = {
+ .fault = cs_char_vma_fault,
+};
+
+static int cs_char_fasync(int fd, struct file *file, int on)
+{
+ struct cs_char *csdata = file->private_data;
+
+ if (fasync_helper(fd, file, on, &csdata->async_queue) < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static unsigned int cs_char_poll(struct file *file, poll_table *wait)
+{
+ struct cs_char *csdata = file->private_data;
+ unsigned int ret = 0;
+
+ poll_wait(file, &cs_char_data.wait, wait);
+ spin_lock_bh(&csdata->lock);
+ if (!list_empty(&csdata->chardev_queue))
+ ret = POLLIN | POLLRDNORM;
+ else if (!list_empty(&csdata->dataind_queue))
+ ret = POLLIN | POLLRDNORM;
+ spin_unlock_bh(&csdata->lock);
+
+ return ret;
+}
+
+static ssize_t cs_char_read(struct file *file, char __user *buf, size_t count,
+ loff_t *unused)
+{
+ struct cs_char *csdata = file->private_data;
+ u32 data;
+ ssize_t retval;
+
+ if (count < sizeof(data))
+ return -EINVAL;
+
+ for (;;) {
+ DEFINE_WAIT(wait);
+
+ spin_lock_bh(&csdata->lock);
+ if (!list_empty(&csdata->chardev_queue)) {
+ data = cs_pop_entry(&csdata->chardev_queue);
+ } else if (!list_empty(&csdata->dataind_queue)) {
+ data = cs_pop_entry(&csdata->dataind_queue);
+ csdata->dataind_pending--;
+ } else {
+ data = 0;
+ }
+ spin_unlock_bh(&csdata->lock);
+
+ if (data)
+ break;
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto out;
+ } else if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ goto out;
+ }
+ prepare_to_wait_exclusive(&csdata->wait, &wait,
+ TASK_INTERRUPTIBLE);
+ schedule();
+ finish_wait(&csdata->wait, &wait);
+ }
+
+ retval = put_user(data, (u32 __user *)buf);
+ if (!retval)
+ retval = sizeof(data);
+
+out:
+ return retval;
+}
+
+static ssize_t cs_char_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *unused)
+{
+ struct cs_char *csdata = file->private_data;
+ u32 data;
+ int err;
+ ssize_t retval;
+
+ if (count < sizeof(data))
+ return -EINVAL;
+
+ if (get_user(data, (u32 __user *)buf))
+ retval = -EFAULT;
+ else
+ retval = count;
+
+ err = cs_hsi_command(csdata->hi, data);
+ if (err < 0)
+ retval = err;
+
+ return retval;
+}
+
+static long cs_char_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct cs_char *csdata = file->private_data;
+ int r = 0;
+
+ switch (cmd) {
+ case CS_GET_STATE: {
+ unsigned int state;
+
+ state = cs_hsi_get_state(csdata->hi);
+ if (copy_to_user((void __user *)arg, &state, sizeof(state)))
+ r = -EFAULT;
+
+ break;
+ }
+ case CS_SET_WAKELINE: {
+ unsigned int state;
+
+ if (copy_from_user(&state, (void __user *)arg, sizeof(state))) {
+ r = -EFAULT;
+ break;
+ }
+
+ if (state > 1) {
+ r = -EINVAL;
+ break;
+ }
+
+ cs_hsi_set_wakeline(csdata->hi, !!state);
+
+ break;
+ }
+ case CS_GET_IF_VERSION: {
+ unsigned int ifver = CS_IF_VERSION;
+
+ if (copy_to_user((void __user *)arg, &ifver, sizeof(ifver)))
+ r = -EFAULT;
+
+ break;
+ }
+ case CS_CONFIG_BUFS: {
+ struct cs_buffer_config buf_cfg;
+
+ if (copy_from_user(&buf_cfg, (void __user *)arg,
+ sizeof(buf_cfg)))
+ r = -EFAULT;
+ else
+ r = cs_hsi_buf_config(csdata->hi, &buf_cfg);
+
+ break;
+ }
+ default:
+ r = -ENOTTY;
+ break;
+ }
+
+ return r;
+}
+
+static int cs_char_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ if (vma->vm_end < vma->vm_start)
+ return -EINVAL;
+
+ if (((vma->vm_end - vma->vm_start) >> PAGE_SHIFT) != 1)
+ return -EINVAL;
+
+ vma->vm_flags |= VM_IO | VM_DONTDUMP | VM_DONTEXPAND;
+ vma->vm_ops = &cs_char_vm_ops;
+ vma->vm_private_data = file->private_data;
+
+ return 0;
+}
+
+static int cs_char_open(struct inode *unused, struct file *file)
+{
+ int ret = 0;
+ unsigned long p;
+
+ spin_lock_bh(&cs_char_data.lock);
+ if (cs_char_data.opened) {
+ ret = -EBUSY;
+ spin_unlock_bh(&cs_char_data.lock);
+ goto out1;
+ }
+ cs_char_data.opened = 1;
+ cs_char_data.dataind_pending = 0;
+ spin_unlock_bh(&cs_char_data.lock);
+
+ p = get_zeroed_page(GFP_KERNEL);
+ if (!p) {
+ ret = -ENOMEM;
+ goto out2;
+ }
+
+ ret = cs_hsi_start(&cs_char_data.hi, cs_char_data.cl, p, CS_MMAP_SIZE);
+ if (ret) {
+ dev_err(&cs_char_data.cl->device, "Unable to initialize HSI\n");
+ goto out3;
+ }
+
+ /* these are only used in release so lock not needed */
+ cs_char_data.mmap_base = p;
+ cs_char_data.mmap_size = CS_MMAP_SIZE;
+
+ file->private_data = &cs_char_data;
+
+ return 0;
+
+out3:
+ free_page(p);
+out2:
+ spin_lock_bh(&cs_char_data.lock);
+ cs_char_data.opened = 0;
+ spin_unlock_bh(&cs_char_data.lock);
+out1:
+ return ret;
+}
+
+static void cs_free_char_queue(struct list_head *head)
+{
+ struct char_queue *entry;
+ struct list_head *cursor, *next;
+
+ if (!list_empty(head)) {
+ list_for_each_safe(cursor, next, head) {
+ entry = list_entry(cursor, struct char_queue, list);
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ }
+
+}
+
+static int cs_char_release(struct inode *unused, struct file *file)
+{
+ struct cs_char *csdata = file->private_data;
+
+ cs_hsi_stop(csdata->hi);
+ spin_lock_bh(&csdata->lock);
+ csdata->hi = NULL;
+ free_page(csdata->mmap_base);
+ cs_free_char_queue(&csdata->chardev_queue);
+ cs_free_char_queue(&csdata->dataind_queue);
+ csdata->opened = 0;
+ spin_unlock_bh(&csdata->lock);
+
+ return 0;
+}
+
+static const struct file_operations cs_char_fops = {
+ .owner = THIS_MODULE,
+ .read = cs_char_read,
+ .write = cs_char_write,
+ .poll = cs_char_poll,
+ .unlocked_ioctl = cs_char_ioctl,
+ .mmap = cs_char_mmap,
+ .open = cs_char_open,
+ .release = cs_char_release,
+ .fasync = cs_char_fasync,
+};
+
+static struct miscdevice cs_char_miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "cmt_speech",
+ .fops = &cs_char_fops
+};
+
+static int cs_hsi_client_probe(struct device *dev)
+{
+ int err = 0;
+ struct hsi_client *cl = to_hsi_client(dev);
+
+ dev_dbg(dev, "hsi_client_probe\n");
+ init_waitqueue_head(&cs_char_data.wait);
+ spin_lock_init(&cs_char_data.lock);
+ cs_char_data.opened = 0;
+ cs_char_data.cl = cl;
+ cs_char_data.hi = NULL;
+ INIT_LIST_HEAD(&cs_char_data.chardev_queue);
+ INIT_LIST_HEAD(&cs_char_data.dataind_queue);
+
+ cs_char_data.channel_id_cmd = hsi_get_channel_id_by_name(cl,
+ "speech-control");
+ if (cs_char_data.channel_id_cmd < 0) {
+ err = cs_char_data.channel_id_cmd;
+ dev_err(dev, "Could not get cmd channel (%d)\n", err);
+ return err;
+ }
+
+ cs_char_data.channel_id_data = hsi_get_channel_id_by_name(cl,
+ "speech-data");
+ if (cs_char_data.channel_id_data < 0) {
+ err = cs_char_data.channel_id_data;
+ dev_err(dev, "Could not get data channel (%d)\n", err);
+ return err;
+ }
+
+ err = misc_register(&cs_char_miscdev);
+ if (err)
+ dev_err(dev, "Failed to register: %d\n", err);
+
+ return err;
+}
+
+static int cs_hsi_client_remove(struct device *dev)
+{
+ struct cs_hsi_iface *hi;
+
+ dev_dbg(dev, "hsi_client_remove\n");
+ misc_deregister(&cs_char_miscdev);
+ spin_lock_bh(&cs_char_data.lock);
+ hi = cs_char_data.hi;
+ cs_char_data.hi = NULL;
+ spin_unlock_bh(&cs_char_data.lock);
+ if (hi)
+ cs_hsi_stop(hi);
+
+ return 0;
+}
+
+static struct hsi_client_driver cs_hsi_driver = {
+ .driver = {
+ .name = "cmt-speech",
+ .owner = THIS_MODULE,
+ .probe = cs_hsi_client_probe,
+ .remove = cs_hsi_client_remove,
+ },
+};
+
+static int __init cs_char_init(void)
+{
+ pr_info("CMT speech driver added\n");
+ return hsi_register_client_driver(&cs_hsi_driver);
+}
+module_init(cs_char_init);
+
+static void __exit cs_char_exit(void)
+{
+ hsi_unregister_client_driver(&cs_hsi_driver);
+ pr_info("CMT speech driver removed\n");
+}
+module_exit(cs_char_exit);
+
+MODULE_ALIAS("hsi:cmt-speech");
+MODULE_AUTHOR("Kai Vehmanen <kai.vehmanen-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>");
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>");
+MODULE_DESCRIPTION("CMT speech driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/hsi/Kbuild b/include/uapi/linux/hsi/Kbuild
index 30ab3cd..a16a005 100644
--- a/include/uapi/linux/hsi/Kbuild
+++ b/include/uapi/linux/hsi/Kbuild
@@ -1,2 +1,2 @@
# UAPI Header export list
-header-y += hsi_char.h
+header-y += hsi_char.h cs-protocol.h
diff --git a/include/uapi/linux/hsi/cs-protocol.h b/include/uapi/linux/hsi/cs-protocol.h
new file mode 100644
index 0000000..4957bba
--- /dev/null
+++ b/include/uapi/linux/hsi/cs-protocol.h
@@ -0,0 +1,113 @@
+/*
+ * cmt-speech interface definitions
+ *
+ * Copyright (C) 2008,2009,2010 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Kai Vehmanen <kai.vehmanen-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
+ * Original author: Peter Ujfalusi <peter.ujfalusi-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef _CS_PROTOCOL_H
+#define _CS_PROTOCOL_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/* chardev parameters */
+#define CS_DEV_FILE_NAME "/dev/cmt_speech"
+
+/* user-space API versioning */
+#define CS_IF_VERSION 2
+
+/* APE kernel <-> user space messages */
+#define CS_CMD_SHIFT 28
+#define CS_DOMAIN_SHIFT 24
+
+#define CS_CMD_MASK 0xff000000
+#define CS_PARAM_MASK 0xffffff
+
+#define CS_CMD(id, dom) \
+ (((id) << CS_CMD_SHIFT) | ((dom) << CS_DOMAIN_SHIFT))
+
+#define CS_ERROR CS_CMD(1, 0)
+#define CS_RX_DATA_RECEIVED CS_CMD(2, 0)
+#define CS_TX_DATA_READY CS_CMD(3, 0)
+#define CS_TX_DATA_SENT CS_CMD(4, 0)
+
+/* params to CS_ERROR indication */
+#define CS_ERR_PEER_RESET 0
+
+/* ioctl interface */
+
+/* parameters to CS_CONFIG_BUFS ioctl */
+#define CS_FEAT_TSTAMP_RX_CTRL (1 << 0)
+#define CS_FEAT_ROLLING_RX_COUNTER (2 << 0)
+
+/* parameters to CS_GET_STATE ioctl */
+#define CS_STATE_CLOSED 0
+#define CS_STATE_OPENED 1 /* resource allocated */
+#define CS_STATE_CONFIGURED 2 /* data path active */
+
+/* maximum number of TX/RX buffers */
+#define CS_MAX_BUFFERS_SHIFT 4
+#define CS_MAX_BUFFERS (1 << CS_MAX_BUFFERS_SHIFT)
+
+/* Parameters for setting up the data buffers */
+struct cs_buffer_config {
+ __u32 rx_bufs; /* number of RX buffer slots */
+ __u32 tx_bufs; /* number of TX buffer slots */
+ __u32 buf_size; /* bytes */
+ __u32 flags; /* see CS_FEAT_* */
+ __u32 reserved[4];
+};
+
+/*
+ * Struct describing the layout and contents of the driver mmap area.
+ * This information is meant as read-only information for the application.
+ */
+struct cs_mmap_config_block {
+ __u32 reserved1;
+ __u32 buf_size; /* 0=disabled, otherwise the transfer size */
+ __u32 rx_bufs; /* # of RX buffers */
+ __u32 tx_bufs; /* # of TX buffers */
+ __u32 reserved2;
+ /* array of offsets within the mmap area for each RX and TX buffer */
+ __u32 rx_offsets[CS_MAX_BUFFERS];
+ __u32 tx_offsets[CS_MAX_BUFFERS];
+ __u32 rx_ptr;
+ __u32 rx_ptr_boundary;
+ __u32 reserved3[2];
+ /*
+ * if enabled with CS_FEAT_TSTAMP_RX_CTRL, monotonic
+ * timestamp taken when the last control command was received
+ */
+ struct timespec tstamp_rx_ctrl;
+};
+
+#define CS_IO_MAGIC 'C'
+
+#define CS_IOW(num, dtype) _IOW(CS_IO_MAGIC, num, dtype)
+#define CS_IOR(num, dtype) _IOR(CS_IO_MAGIC, num, dtype)
+#define CS_IOWR(num, dtype) _IOWR(CS_IO_MAGIC, num, dtype)
+#define CS_IO(num) _IO(CS_IO_MAGIC, num)
+
+#define CS_GET_STATE CS_IOR(21, unsigned int)
+#define CS_SET_WAKELINE CS_IOW(23, unsigned int)
+#define CS_GET_IF_VERSION CS_IOR(30, unsigned int)
+#define CS_CONFIG_BUFS CS_IOW(31, struct cs_buffer_config)
+
+#endif /* _CS_PROTOCOL_H */
--
2.1.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox