summaryrefslogtreecommitdiff
path: root/drivers/hid/bpf
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/bpf')
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.c116
-rw-r--r--drivers/hid/bpf/hid_bpf_jmp_table.c4
2 files changed, 109 insertions, 11 deletions
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index 0ef99caf937a..efd4e4b4a615 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -28,8 +28,9 @@ EXPORT_SYMBOL(hid_bpf_ops);
*
* @ctx: The HID-BPF context
*
- * @return %0 on success and keep processing; a negative error code to interrupt
- * the processing of this event
+ * @return %0 on success and keep processing; a positive value to change the
+ * incoming size buffer; a negative error code to interrupt the processing
+ * of this event
*
* Declare an %fmod_ret tracing bpf program to this function and attach this
* program through hid_bpf_attach_prog() to have this helper called for
@@ -44,23 +45,43 @@ __weak noinline int hid_bpf_device_event(struct hid_bpf_ctx *ctx)
}
ALLOW_ERROR_INJECTION(hid_bpf_device_event, ERRNO);
-int
+u8 *
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
- u32 size, int interrupt)
+ u32 *size, int interrupt)
{
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
.hid = hdev,
.report_type = type,
- .size = size,
+ .allocated_size = hdev->bpf.allocated_data,
+ .size = *size,
},
- .data = data,
+ .data = hdev->bpf.device_data,
};
+ int ret;
if (type >= HID_REPORT_TYPES)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
+
+ /* no program has been attached yet */
+ if (!hdev->bpf.device_data)
+ return data;
+
+ memset(ctx_kern.data, 0, hdev->bpf.allocated_data);
+ memcpy(ctx_kern.data, data, *size);
+
+ ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_DEVICE_EVENT, &ctx_kern);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ if (ret) {
+ if (ret > ctx_kern.ctx.allocated_size)
+ return ERR_PTR(-EINVAL);
- return hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_DEVICE_EVENT, &ctx_kern);
+ *size = ret;
+ }
+
+ return ctx_kern.data;
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
@@ -83,7 +104,7 @@ hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
- if (rdwr_buf_size + offset > ctx->size)
+ if (rdwr_buf_size + offset > ctx->allocated_size)
return NULL;
return ctx_kern->data + offset;
@@ -110,6 +131,51 @@ static int device_match_id(struct device *dev, const void *id)
return hdev->id == *(int *)id;
}
+static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size)
+{
+ u8 *alloc_data;
+ unsigned int i, j, max_report_len = 0;
+ size_t alloc_size = 0;
+
+ /* compute the maximum report length for this device */
+ for (i = 0; i < HID_REPORT_TYPES; i++) {
+ struct hid_report_enum *report_enum = hdev->report_enum + i;
+
+ for (j = 0; j < HID_MAX_IDS; j++) {
+ struct hid_report *report = report_enum->report_id_hash[j];
+
+ if (report)
+ max_report_len = max(max_report_len, hid_report_len(report));
+ }
+ }
+
+ /*
+ * Give us a little bit of extra space and some predictability in the
+ * buffer length we create. This way, we can tell users that they can
+ * work on chunks of 64 bytes of memory without having the bpf verifier
+ * scream at them.
+ */
+ alloc_size = DIV_ROUND_UP(max_report_len, 64) * 64;
+
+ alloc_data = kzalloc(alloc_size, GFP_KERNEL);
+ if (!alloc_data)
+ return -ENOMEM;
+
+ *data = alloc_data;
+ *size = alloc_size;
+
+ return 0;
+}
+
+static int hid_bpf_allocate_event_data(struct hid_device *hdev)
+{
+ /* hdev->bpf.device_data is already allocated, abort */
+ if (hdev->bpf.device_data)
+ return 0;
+
+ return __hid_bpf_allocate_data(hdev, &hdev->bpf.device_data, &hdev->bpf.allocated_data);
+}
+
/**
* hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
*
@@ -125,7 +191,7 @@ hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
{
struct hid_device *hdev;
struct device *dev;
- int prog_type = hid_bpf_get_prog_attach_type(prog_fd);
+ int err, prog_type = hid_bpf_get_prog_attach_type(prog_fd);
if (!hid_bpf_ops)
return -EINVAL;
@@ -145,6 +211,12 @@ hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
hdev = to_hid_device(dev);
+ if (prog_type == HID_BPF_PROG_TYPE_DEVICE_EVENT) {
+ err = hid_bpf_allocate_event_data(hdev);
+ if (err)
+ return err;
+ }
+
return __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
}
@@ -158,6 +230,30 @@ static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
.set = &hid_bpf_syscall_kfunc_ids,
};
+int hid_bpf_connect_device(struct hid_device *hdev)
+{
+ struct hid_bpf_prog_list *prog_list;
+
+ rcu_read_lock();
+ prog_list = rcu_dereference(hdev->bpf.progs[HID_BPF_PROG_TYPE_DEVICE_EVENT]);
+ rcu_read_unlock();
+
+ /* only allocate BPF data if there are programs attached */
+ if (!prog_list)
+ return 0;
+
+ return hid_bpf_allocate_event_data(hdev);
+}
+EXPORT_SYMBOL_GPL(hid_bpf_connect_device);
+
+void hid_bpf_disconnect_device(struct hid_device *hdev)
+{
+ kfree(hdev->bpf.device_data);
+ hdev->bpf.device_data = NULL;
+ hdev->bpf.allocated_data = 0;
+}
+EXPORT_SYMBOL_GPL(hid_bpf_disconnect_device);
+
void hid_bpf_destroy_device(struct hid_device *hdev)
{
if (!hdev)
diff --git a/drivers/hid/bpf/hid_bpf_jmp_table.c b/drivers/hid/bpf/hid_bpf_jmp_table.c
index e5f171ab480c..bfcdd7b805a9 100644
--- a/drivers/hid/bpf/hid_bpf_jmp_table.c
+++ b/drivers/hid/bpf/hid_bpf_jmp_table.c
@@ -122,8 +122,10 @@ int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
ctx_kern->ctx.index = idx;
err = __hid_bpf_tail_call(&ctx_kern->ctx);
- if (err)
+ if (err < 0)
break;
+ if (err)
+ ctx_kern->ctx.retval = err;
}
out_unlock: