diff mbox series

[HID,4/7] HID: bpf: allow BPF programs to force using hid-generic

Message ID 20240903-hid-bpf-hid-generic-v1-4-9511a565b2da@kernel.org
State New
Headers show
Series HID: bpf: add a new hook to control hid-generic | expand

Commit Message

Benjamin Tissoires Sept. 2, 2024, 4:14 p.m. UTC
The use case is when we fix a device through HID-BPF, 99% of the cases
we want the device to use hid-generic now instead of a dedicated device.

That's because the dedicated device might also want to change the report
descriptor, or will be handling the device in a different way the new
fixed device is using.

In hid-core, after matching for the device (so that we only call this new
hook on compatible drivers), we call for `.hid_bpf_driver_probe`.
The function can not communicate with the device because it is not yet
started, but it can make educated guesses and decide to:
- let hid-core decide by itself
- force the use of this driver (by comparing the provided name with
  "hid-generic" for instance)
- force hid-core to ignore this driver for this device.

For API stability, we don't rely on a bitfield or a return value for
chosing hid-core behavior. We simply have a couple of writeable fields
in the new struct hid_bpf_driver, and then hid-core can make its educated
decision.

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
 Documentation/hid/hid-bpf.rst        |  2 +-
 drivers/hid/bpf/hid_bpf_dispatch.c   | 31 ++++++++++++++++++++++++++++
 drivers/hid/bpf/hid_bpf_struct_ops.c |  3 +++
 drivers/hid/hid-core.c               |  6 ++++++
 include/linux/hid_bpf.h              | 40 ++++++++++++++++++++++++++++++++++++
 5 files changed, 81 insertions(+), 1 deletion(-)

Comments

Benjamin Tissoires Sept. 3, 2024, 3:05 p.m. UTC | #1
On Sep 03 2024, Peter Hutterer wrote:
> On Tue, Sep 03, 2024 at 01:14:34AM +0900, Benjamin Tissoires wrote:
> > The use case is when we fix a device through HID-BPF, 99% of the cases
> > we want the device to use hid-generic now instead of a dedicated device.
> 
> s/dedicated device/dedicated driver/ in the commit message
> 
> > That's because the dedicated device might also want to change the report
> > descriptor, or will be handling the device in a different way the new
> > fixed device is using.
> > 
> > In hid-core, after matching for the device (so that we only call this new
> > hook on compatible drivers), we call for `.hid_bpf_driver_probe`.
> > The function can not communicate with the device because it is not yet
> > started, but it can make educated guesses and decide to:
> > - let hid-core decide by itself
> > - force the use of this driver (by comparing the provided name with
> >   "hid-generic" for instance)
> > - force hid-core to ignore this driver for this device.
> > 
> > For API stability, we don't rely on a bitfield or a return value for
> > chosing hid-core behavior. We simply have a couple of writeable fields
> > in the new struct hid_bpf_driver, and then hid-core can make its educated
> > decision.
> > 
> > Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> > ---
> >  Documentation/hid/hid-bpf.rst        |  2 +-
> >  drivers/hid/bpf/hid_bpf_dispatch.c   | 31 ++++++++++++++++++++++++++++
> >  drivers/hid/bpf/hid_bpf_struct_ops.c |  3 +++
> >  drivers/hid/hid-core.c               |  6 ++++++
> >  include/linux/hid_bpf.h              | 40 ++++++++++++++++++++++++++++++++++++
> >  5 files changed, 81 insertions(+), 1 deletion(-)
> > 
> > diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
> > index 5939eeafb361..05a43f11cdab 100644
> > --- a/Documentation/hid/hid-bpf.rst
> > +++ b/Documentation/hid/hid-bpf.rst
> > @@ -190,7 +190,7 @@ User API data structures available in programs:
> >  -----------------------------------------------
> >  
> >  .. kernel-doc:: include/linux/hid_bpf.h
> > -   :identifiers: hid_bpf_ctx
> > +   :identifiers: hid_bpf_ctx hid_bpf_driver
> >  
> >  Available API that can be used in all HID-BPF struct_ops programs:
> >  ------------------------------------------------------------------
> > diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
> > index a272a086c950..2df136d64152 100644
> > --- a/drivers/hid/bpf/hid_bpf_dispatch.c
> > +++ b/drivers/hid/bpf/hid_bpf_dispatch.c
> > @@ -189,6 +189,37 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
> >  }
> >  EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
> >  
> > +int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
> > +			      const struct hid_device_id *id)
> > +{
> > +	struct hid_bpf_driver drv = { 0 };
> > +	struct hid_bpf_ops *e;
> > +	int idx;
>  +
> > +	if (strscpy(drv.name, hdrv->name, sizeof(drv.name)) < 0)
> > +		return 0;
> > +
> > +	idx = srcu_read_lock(&hdev->bpf.srcu);
> > +	list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
> > +				 srcu_read_lock_held(&hdev->bpf.srcu)) {
> > +		if (!e->hid_driver_probe)
> > +			continue;
> > +
> > +		e->hid_driver_probe(hdev, &drv, id);
> > +	}
> > +
> > +	srcu_read_unlock(&hdev->bpf.srcu, idx);
> > +
> > +	if (drv.force_driver)
> > +		return 1;
> > +
> > +	if (drv.ignore_driver)
> > +		return -1;
> > +
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(call_hid_bpf_driver_probe);
> > +
> >  static int device_match_id(struct device *dev, const void *id)
> >  {
> >  	struct hid_device *hdev = to_hid_device(dev);
> > diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c
> > index cd696c59ba0f..1e13a22f73a1 100644
> > --- a/drivers/hid/bpf/hid_bpf_struct_ops.c
> > +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c
> > @@ -46,6 +46,7 @@ static int hid_bpf_ops_check_member(const struct btf_type *t,
> >  	case offsetof(struct hid_bpf_ops, hid_rdesc_fixup):
> >  	case offsetof(struct hid_bpf_ops, hid_hw_request):
> >  	case offsetof(struct hid_bpf_ops, hid_hw_output_report):
> > +	case offsetof(struct hid_bpf_ops, hid_driver_probe):
> >  		break;
> >  	default:
> >  		if (prog->sleepable)
> > @@ -79,6 +80,8 @@ static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
> >  		WRITE_RANGE(hid_device, name, true),
> >  		WRITE_RANGE(hid_device, uniq, true),
> >  		WRITE_RANGE(hid_device, phys, true),
> > +		WRITE_RANGE(hid_bpf_driver, force_driver, false),
> > +		WRITE_RANGE(hid_bpf_driver, ignore_driver, false),
> >  	};
> >  #undef WRITE_RANGE
> >  	const struct btf_type *state = NULL;
> > diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> > index 988d0acbdf04..7845f0a789ec 100644
> > --- a/drivers/hid/hid-core.c
> > +++ b/drivers/hid/hid-core.c
> > @@ -2639,10 +2639,16 @@ static bool hid_check_device_match(struct hid_device *hdev,
> >  				   struct hid_driver *hdrv,
> >  				   const struct hid_device_id **id)
> >  {
> > +	int ret;
> > +
> >  	*id = hid_match_device(hdev, hdrv);
> >  	if (!*id)
> >  		return false;
> >  
> > +	ret = call_hid_bpf_driver_probe(hdev, hdrv, *id);
> > +	if (ret)
> > +		return ret > 0;
> > +
> >  	if (hdrv->match)
> >  		return hdrv->match(hdev, hid_ignore_special_drivers);
> >  
> > diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
> > index d4d063cf63b5..20693c218857 100644
> > --- a/include/linux/hid_bpf.h
> > +++ b/include/linux/hid_bpf.h
> > @@ -9,6 +9,7 @@
> >  #include <uapi/linux/hid.h>
> >  
> >  struct hid_device;
> > +struct hid_driver;
> >  
> >  /*
> >   * The following is the user facing HID BPF API.
> > @@ -80,6 +81,22 @@ struct hid_ops {
> >  
> >  extern struct hid_ops *hid_ops;
> >  
> > +/**
> > + * struct hid_bpf_driver - User accessible data for the ``hid_bpf_probe``
> > + * struct_ops
> > + *
> > + * @name: the name of the driver currently being treated
> > + * @force_driver: set this to ``true`` to force hid-core to use this driver,
> > + *		  bypassing any further decision made by this driver
> > + * @ignore_driver: set this to ``true`` to force hid-core to ignore this driver,
> > + *                bypassing any further decision made by this driver
> 
> If I set both to false or true, what happens? The two seem to be

force_driver has priority over ignore_driver.

> mutually exclusive, in userspace I'd use an enum here to have a
> NOOP/FORCE_DRIVER/IGNORE_DRIVER value range (that can be extended later).
> Maybe something like that is an option?

enum also has the advantage to be exported in vmlinux.h.

FWIW, the idea behind adding new fields in a struct was to get the
backward compatibility for free. Because the verifier/relocator will see
if we are using the correct field entries.

OTOH, maybe we can make the function return the afformended enum, and
drop those two fields.

I think we should probably abort processing of any bpf sets the return
value to anything else than NOOP.

I'll work a little bit more on that.

Cheers,
Benjamin

> 
> > + */
> > +struct hid_bpf_driver {
> > +	__u8 name[64];
> > +	bool force_driver;
> > +	bool ignore_driver;
> > +};
> > +
> >  /**
> >   * struct hid_bpf_ops - A BPF struct_ops of callbacks allowing to attach HID-BPF
> >   *			programs to a HID device
> > @@ -178,6 +195,25 @@ struct hid_bpf_ops {
> >  	 */
> >  	int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source);
> >  
> > +	/**
> > +	 * @hid_driver_probe: called before the kernel ``.probe()`` function
> > +	 *
> > +	 * It has the following arguments:
> > +	 *
> > +	 * ``hdev``: The HID device kernel representation
> > +	 *
> > +	 * ``hdrv``: A BPF partially writeable representation of a HID driver
> > +	 *
> > +	 * ``id``: The device match structure found in the driver
> > +	 *
> > +	 * Note that the device has not been started yet, and thus kfuncs like
> > +	 * ``hid_hw_output_report`` will likely fail.
> 
> Just to confirm, I can access the device's report descriptor though? For
> the devices that we're looking at (e.g. the foot pedals pretending to be
> an apple keyboard) the driver name and what we can set in HID_BPF_CONFIG
> are not going to be enough, we'll have to check the rdesc too.
> 
> Cheers,
>   Peter
> 
> > +	 *
> > +	 * This function is useful to force/ignore a given supported HID driver,
> > +	 * by writing ``true`` in ``hdrv->force_driver`` or ``hdrv->ignore_driver``
> > +	 */
> > +	void (*hid_driver_probe)(struct hid_device *hdev, struct hid_bpf_driver *hdrv,
> > +				 const struct hid_device_id *id);
> >  
> >  	/* private: do not show up in the docs */
> >  	struct hid_device *hdev;
> > @@ -213,6 +249,8 @@ void hid_bpf_disconnect_device(struct hid_device *hdev);
> >  void hid_bpf_destroy_device(struct hid_device *hid);
> >  int hid_bpf_device_init(struct hid_device *hid);
> >  u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size);
> > +int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
> > +			      const struct hid_device_id *id);
> >  #else /* CONFIG_HID_BPF */
> >  static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
> >  						u8 *data, u32 *size, int interrupt,
> > @@ -228,6 +266,8 @@ static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
> >  static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
> >  static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
> >  static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
> > +static inline int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
> > +					    const struct hid_device_id *id) { return 0; }
> >  /*
> >   * This specialized allocator has to be a macro for its allocations to be
> >   * accounted separately (to have a separate alloc_tag). The typecast is
> > 
> > -- 
> > 2.46.0
> >
Benjamin Tissoires Sept. 4, 2024, 9:28 a.m. UTC | #2
On Sep 03 2024, Benjamin Tissoires wrote:
> On Sep 03 2024, Peter Hutterer wrote:
> > On Tue, Sep 03, 2024 at 01:14:34AM +0900, Benjamin Tissoires wrote:
> > > The use case is when we fix a device through HID-BPF, 99% of the cases
> > > we want the device to use hid-generic now instead of a dedicated device.
> > 
> > s/dedicated device/dedicated driver/ in the commit message
> > 
> > > That's because the dedicated device might also want to change the report
> > > descriptor, or will be handling the device in a different way the new
> > > fixed device is using.
> > > 
> > > In hid-core, after matching for the device (so that we only call this new
> > > hook on compatible drivers), we call for `.hid_bpf_driver_probe`.
> > > The function can not communicate with the device because it is not yet
> > > started, but it can make educated guesses and decide to:
> > > - let hid-core decide by itself
> > > - force the use of this driver (by comparing the provided name with
> > >   "hid-generic" for instance)
> > > - force hid-core to ignore this driver for this device.
> > > 
> > > For API stability, we don't rely on a bitfield or a return value for
> > > chosing hid-core behavior. We simply have a couple of writeable fields
> > > in the new struct hid_bpf_driver, and then hid-core can make its educated
> > > decision.
> > > 
> > > Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> > > ---
> > >  Documentation/hid/hid-bpf.rst        |  2 +-
> > >  drivers/hid/bpf/hid_bpf_dispatch.c   | 31 ++++++++++++++++++++++++++++
> > >  drivers/hid/bpf/hid_bpf_struct_ops.c |  3 +++
> > >  drivers/hid/hid-core.c               |  6 ++++++
> > >  include/linux/hid_bpf.h              | 40 ++++++++++++++++++++++++++++++++++++
> > >  5 files changed, 81 insertions(+), 1 deletion(-)
> > > 
> > > diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
> > > index 5939eeafb361..05a43f11cdab 100644
> > > --- a/Documentation/hid/hid-bpf.rst
> > > +++ b/Documentation/hid/hid-bpf.rst
> > > @@ -190,7 +190,7 @@ User API data structures available in programs:
> > >  -----------------------------------------------
> > >  
> > >  .. kernel-doc:: include/linux/hid_bpf.h
> > > -   :identifiers: hid_bpf_ctx
> > > +   :identifiers: hid_bpf_ctx hid_bpf_driver
> > >  
> > >  Available API that can be used in all HID-BPF struct_ops programs:
> > >  ------------------------------------------------------------------
> > > diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
> > > index a272a086c950..2df136d64152 100644
> > > --- a/drivers/hid/bpf/hid_bpf_dispatch.c
> > > +++ b/drivers/hid/bpf/hid_bpf_dispatch.c
> > > @@ -189,6 +189,37 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
> > >  }
> > >  EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
> > >  
> > > +int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
> > > +			      const struct hid_device_id *id)
> > > +{
> > > +	struct hid_bpf_driver drv = { 0 };
> > > +	struct hid_bpf_ops *e;
> > > +	int idx;
> >  +
> > > +	if (strscpy(drv.name, hdrv->name, sizeof(drv.name)) < 0)
> > > +		return 0;
> > > +
> > > +	idx = srcu_read_lock(&hdev->bpf.srcu);
> > > +	list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
> > > +				 srcu_read_lock_held(&hdev->bpf.srcu)) {
> > > +		if (!e->hid_driver_probe)
> > > +			continue;
> > > +
> > > +		e->hid_driver_probe(hdev, &drv, id);
> > > +	}
> > > +
> > > +	srcu_read_unlock(&hdev->bpf.srcu, idx);
> > > +
> > > +	if (drv.force_driver)
> > > +		return 1;
> > > +
> > > +	if (drv.ignore_driver)
> > > +		return -1;
> > > +
> > > +	return 0;
> > > +}
> > > +EXPORT_SYMBOL_GPL(call_hid_bpf_driver_probe);
> > > +
> > >  static int device_match_id(struct device *dev, const void *id)
> > >  {
> > >  	struct hid_device *hdev = to_hid_device(dev);
> > > diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c
> > > index cd696c59ba0f..1e13a22f73a1 100644
> > > --- a/drivers/hid/bpf/hid_bpf_struct_ops.c
> > > +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c
> > > @@ -46,6 +46,7 @@ static int hid_bpf_ops_check_member(const struct btf_type *t,
> > >  	case offsetof(struct hid_bpf_ops, hid_rdesc_fixup):
> > >  	case offsetof(struct hid_bpf_ops, hid_hw_request):
> > >  	case offsetof(struct hid_bpf_ops, hid_hw_output_report):
> > > +	case offsetof(struct hid_bpf_ops, hid_driver_probe):
> > >  		break;
> > >  	default:
> > >  		if (prog->sleepable)
> > > @@ -79,6 +80,8 @@ static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
> > >  		WRITE_RANGE(hid_device, name, true),
> > >  		WRITE_RANGE(hid_device, uniq, true),
> > >  		WRITE_RANGE(hid_device, phys, true),
> > > +		WRITE_RANGE(hid_bpf_driver, force_driver, false),
> > > +		WRITE_RANGE(hid_bpf_driver, ignore_driver, false),
> > >  	};
> > >  #undef WRITE_RANGE
> > >  	const struct btf_type *state = NULL;
> > > diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> > > index 988d0acbdf04..7845f0a789ec 100644
> > > --- a/drivers/hid/hid-core.c
> > > +++ b/drivers/hid/hid-core.c
> > > @@ -2639,10 +2639,16 @@ static bool hid_check_device_match(struct hid_device *hdev,
> > >  				   struct hid_driver *hdrv,
> > >  				   const struct hid_device_id **id)
> > >  {
> > > +	int ret;
> > > +
> > >  	*id = hid_match_device(hdev, hdrv);
> > >  	if (!*id)
> > >  		return false;
> > >  
> > > +	ret = call_hid_bpf_driver_probe(hdev, hdrv, *id);
> > > +	if (ret)
> > > +		return ret > 0;
> > > +
> > >  	if (hdrv->match)
> > >  		return hdrv->match(hdev, hid_ignore_special_drivers);
> > >  
> > > diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
> > > index d4d063cf63b5..20693c218857 100644
> > > --- a/include/linux/hid_bpf.h
> > > +++ b/include/linux/hid_bpf.h
> > > @@ -9,6 +9,7 @@
> > >  #include <uapi/linux/hid.h>
> > >  
> > >  struct hid_device;
> > > +struct hid_driver;
> > >  
> > >  /*
> > >   * The following is the user facing HID BPF API.
> > > @@ -80,6 +81,22 @@ struct hid_ops {
> > >  
> > >  extern struct hid_ops *hid_ops;
> > >  
> > > +/**
> > > + * struct hid_bpf_driver - User accessible data for the ``hid_bpf_probe``
> > > + * struct_ops
> > > + *
> > > + * @name: the name of the driver currently being treated
> > > + * @force_driver: set this to ``true`` to force hid-core to use this driver,
> > > + *		  bypassing any further decision made by this driver
> > > + * @ignore_driver: set this to ``true`` to force hid-core to ignore this driver,
> > > + *                bypassing any further decision made by this driver
> > 
> > If I set both to false or true, what happens? The two seem to be
> 
> force_driver has priority over ignore_driver.
> 
> > mutually exclusive, in userspace I'd use an enum here to have a
> > NOOP/FORCE_DRIVER/IGNORE_DRIVER value range (that can be extended later).
> > Maybe something like that is an option?
> 
> enum also has the advantage to be exported in vmlinux.h.
> 
> FWIW, the idea behind adding new fields in a struct was to get the
> backward compatibility for free. Because the verifier/relocator will see
> if we are using the correct field entries.
> 
> OTOH, maybe we can make the function return the afformended enum, and
> drop those two fields.
> 
> I think we should probably abort processing of any bpf sets the return
> value to anything else than NOOP.
> 
> I'll work a little bit more on that.
> 
> Cheers,
> Benjamin
> 
> > 
> > > + */
> > > +struct hid_bpf_driver {
> > > +	__u8 name[64];
> > > +	bool force_driver;
> > > +	bool ignore_driver;
> > > +};
> > > +
> > >  /**
> > >   * struct hid_bpf_ops - A BPF struct_ops of callbacks allowing to attach HID-BPF
> > >   *			programs to a HID device
> > > @@ -178,6 +195,25 @@ struct hid_bpf_ops {
> > >  	 */
> > >  	int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source);
> > >  
> > > +	/**
> > > +	 * @hid_driver_probe: called before the kernel ``.probe()`` function
> > > +	 *
> > > +	 * It has the following arguments:
> > > +	 *
> > > +	 * ``hdev``: The HID device kernel representation
> > > +	 *
> > > +	 * ``hdrv``: A BPF partially writeable representation of a HID driver
> > > +	 *
> > > +	 * ``id``: The device match structure found in the driver
> > > +	 *
> > > +	 * Note that the device has not been started yet, and thus kfuncs like
> > > +	 * ``hid_hw_output_report`` will likely fail.
> > 
> > Just to confirm, I can access the device's report descriptor though? For

I forgot to reply to this comment:
no, you don't :)

> > the devices that we're looking at (e.g. the foot pedals pretending to be
> > an apple keyboard) the driver name and what we can set in HID_BPF_CONFIG
> > are not going to be enough, we'll have to check the rdesc too.

You can check this in the probe syscall before unbinding/rebinding the
device. The device is uniquely linked to the bpf program you loaded, so
in theory this is sufficient.

Cheers,
Benjamin

> > 
> > Cheers,
> >   Peter
> > 
> > > +	 *
> > > +	 * This function is useful to force/ignore a given supported HID driver,
> > > +	 * by writing ``true`` in ``hdrv->force_driver`` or ``hdrv->ignore_driver``
> > > +	 */
> > > +	void (*hid_driver_probe)(struct hid_device *hdev, struct hid_bpf_driver *hdrv,
> > > +				 const struct hid_device_id *id);
> > >  
> > >  	/* private: do not show up in the docs */
> > >  	struct hid_device *hdev;
> > > @@ -213,6 +249,8 @@ void hid_bpf_disconnect_device(struct hid_device *hdev);
> > >  void hid_bpf_destroy_device(struct hid_device *hid);
> > >  int hid_bpf_device_init(struct hid_device *hid);
> > >  u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size);
> > > +int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
> > > +			      const struct hid_device_id *id);
> > >  #else /* CONFIG_HID_BPF */
> > >  static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
> > >  						u8 *data, u32 *size, int interrupt,
> > > @@ -228,6 +266,8 @@ static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
> > >  static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
> > >  static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
> > >  static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
> > > +static inline int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
> > > +					    const struct hid_device_id *id) { return 0; }
> > >  /*
> > >   * This specialized allocator has to be a macro for its allocations to be
> > >   * accounted separately (to have a separate alloc_tag). The typecast is
> > > 
> > > -- 
> > > 2.46.0
> > >
diff mbox series

Patch

diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
index 5939eeafb361..05a43f11cdab 100644
--- a/Documentation/hid/hid-bpf.rst
+++ b/Documentation/hid/hid-bpf.rst
@@ -190,7 +190,7 @@  User API data structures available in programs:
 -----------------------------------------------
 
 .. kernel-doc:: include/linux/hid_bpf.h
-   :identifiers: hid_bpf_ctx
+   :identifiers: hid_bpf_ctx hid_bpf_driver
 
 Available API that can be used in all HID-BPF struct_ops programs:
 ------------------------------------------------------------------
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index a272a086c950..2df136d64152 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -189,6 +189,37 @@  u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
 }
 EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
 
+int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
+			      const struct hid_device_id *id)
+{
+	struct hid_bpf_driver drv = { 0 };
+	struct hid_bpf_ops *e;
+	int idx;
+
+	if (strscpy(drv.name, hdrv->name, sizeof(drv.name)) < 0)
+		return 0;
+
+	idx = srcu_read_lock(&hdev->bpf.srcu);
+	list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
+				 srcu_read_lock_held(&hdev->bpf.srcu)) {
+		if (!e->hid_driver_probe)
+			continue;
+
+		e->hid_driver_probe(hdev, &drv, id);
+	}
+
+	srcu_read_unlock(&hdev->bpf.srcu, idx);
+
+	if (drv.force_driver)
+		return 1;
+
+	if (drv.ignore_driver)
+		return -1;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(call_hid_bpf_driver_probe);
+
 static int device_match_id(struct device *dev, const void *id)
 {
 	struct hid_device *hdev = to_hid_device(dev);
diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c
index cd696c59ba0f..1e13a22f73a1 100644
--- a/drivers/hid/bpf/hid_bpf_struct_ops.c
+++ b/drivers/hid/bpf/hid_bpf_struct_ops.c
@@ -46,6 +46,7 @@  static int hid_bpf_ops_check_member(const struct btf_type *t,
 	case offsetof(struct hid_bpf_ops, hid_rdesc_fixup):
 	case offsetof(struct hid_bpf_ops, hid_hw_request):
 	case offsetof(struct hid_bpf_ops, hid_hw_output_report):
+	case offsetof(struct hid_bpf_ops, hid_driver_probe):
 		break;
 	default:
 		if (prog->sleepable)
@@ -79,6 +80,8 @@  static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
 		WRITE_RANGE(hid_device, name, true),
 		WRITE_RANGE(hid_device, uniq, true),
 		WRITE_RANGE(hid_device, phys, true),
+		WRITE_RANGE(hid_bpf_driver, force_driver, false),
+		WRITE_RANGE(hid_bpf_driver, ignore_driver, false),
 	};
 #undef WRITE_RANGE
 	const struct btf_type *state = NULL;
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 988d0acbdf04..7845f0a789ec 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2639,10 +2639,16 @@  static bool hid_check_device_match(struct hid_device *hdev,
 				   struct hid_driver *hdrv,
 				   const struct hid_device_id **id)
 {
+	int ret;
+
 	*id = hid_match_device(hdev, hdrv);
 	if (!*id)
 		return false;
 
+	ret = call_hid_bpf_driver_probe(hdev, hdrv, *id);
+	if (ret)
+		return ret > 0;
+
 	if (hdrv->match)
 		return hdrv->match(hdev, hid_ignore_special_drivers);
 
diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
index d4d063cf63b5..20693c218857 100644
--- a/include/linux/hid_bpf.h
+++ b/include/linux/hid_bpf.h
@@ -9,6 +9,7 @@ 
 #include <uapi/linux/hid.h>
 
 struct hid_device;
+struct hid_driver;
 
 /*
  * The following is the user facing HID BPF API.
@@ -80,6 +81,22 @@  struct hid_ops {
 
 extern struct hid_ops *hid_ops;
 
+/**
+ * struct hid_bpf_driver - User accessible data for the ``hid_bpf_probe``
+ * struct_ops
+ *
+ * @name: the name of the driver currently being treated
+ * @force_driver: set this to ``true`` to force hid-core to use this driver,
+ *		  bypassing any further decision made by this driver
+ * @ignore_driver: set this to ``true`` to force hid-core to ignore this driver,
+ *                bypassing any further decision made by this driver
+ */
+struct hid_bpf_driver {
+	__u8 name[64];
+	bool force_driver;
+	bool ignore_driver;
+};
+
 /**
  * struct hid_bpf_ops - A BPF struct_ops of callbacks allowing to attach HID-BPF
  *			programs to a HID device
@@ -178,6 +195,25 @@  struct hid_bpf_ops {
 	 */
 	int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source);
 
+	/**
+	 * @hid_driver_probe: called before the kernel ``.probe()`` function
+	 *
+	 * It has the following arguments:
+	 *
+	 * ``hdev``: The HID device kernel representation
+	 *
+	 * ``hdrv``: A BPF partially writeable representation of a HID driver
+	 *
+	 * ``id``: The device match structure found in the driver
+	 *
+	 * Note that the device has not been started yet, and thus kfuncs like
+	 * ``hid_hw_output_report`` will likely fail.
+	 *
+	 * This function is useful to force/ignore a given supported HID driver,
+	 * by writing ``true`` in ``hdrv->force_driver`` or ``hdrv->ignore_driver``
+	 */
+	void (*hid_driver_probe)(struct hid_device *hdev, struct hid_bpf_driver *hdrv,
+				 const struct hid_device_id *id);
 
 	/* private: do not show up in the docs */
 	struct hid_device *hdev;
@@ -213,6 +249,8 @@  void hid_bpf_disconnect_device(struct hid_device *hdev);
 void hid_bpf_destroy_device(struct hid_device *hid);
 int hid_bpf_device_init(struct hid_device *hid);
 u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size);
+int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
+			      const struct hid_device_id *id);
 #else /* CONFIG_HID_BPF */
 static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
 						u8 *data, u32 *size, int interrupt,
@@ -228,6 +266,8 @@  static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
 static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
 static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
 static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
+static inline int call_hid_bpf_driver_probe(struct hid_device *hdev, struct hid_driver *hdrv,
+					    const struct hid_device_id *id) { return 0; }
 /*
  * This specialized allocator has to be a macro for its allocations to be
  * accounted separately (to have a separate alloc_tag). The typecast is