lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <CAOOg__BnRj78Govo8Dchtxng7uBB30ZUdD2zJhZYszQgfB9N2g@mail.gmail.com>
Date: Sun, 16 Nov 2025 11:54:32 +0000
From: Lucas Zampieri <lzampier@...hat.com>
To: Benjamin Tissoires <bentiss@...nel.org>
Cc: linux-input@...r.kernel.org, linux-kernel@...r.kernel.org, 
	Jiri Kosina <jikos@...nel.org>, Sebastian Reichel <sre@...nel.org>, Bastien Nocera <hadess@...ess.net>, 
	linux-pm@...r.kernel.org
Subject: Re: [RFC PATCH v2 3/3] HID: input: Add support for multiple batteries
 per device

On Thu, Nov 13, 2025 at 11:08 AM Benjamin Tissoires <bentiss@...nel.org> wrote:
>
> On Nov 13 2025, Lucas Zampieri wrote:
> > Enable HID devices to register and manage multiple batteries by
> > maintaining a list of hid_battery structures, each identified by
> > its report ID.
> >
> > The legacy dev->battery field and related fields are maintained for
> > backward compatibility, pointing to the first battery in the list.
> > This allows existing code to continue working unchanged while
> > enabling new functionality for multi-battery devices.
> >
> > Example hardware that can benefit from this:
> > - Gaming headsets with charging docks (e.g., SteelSeries Arctis Nova Pro
> >   Wireless)
> > - Graphics tablets with stylus batteries (Wacom)
> > - Wireless earbuds with per-earbud batteries plus charging case
> > - Split keyboards with independent battery per side
> >
> > Signed-off-by: Lucas Zampieri <lzampier@...hat.com>
> > ---
> >  drivers/hid/hid-core.c  |  4 ++
> >  drivers/hid/hid-input.c | 99 +++++++++++++++++++++++++++--------------
> >  include/linux/hid.h     | 12 ++++-
> >  3 files changed, 80 insertions(+), 35 deletions(-)
> >
> > diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> > index a5b3a8ca2fcb..76d628547e9a 100644
> > --- a/drivers/hid/hid-core.c
> > +++ b/drivers/hid/hid-core.c
> > @@ -2990,6 +2990,10 @@ struct hid_device *hid_allocate_device(void)
> >       mutex_init(&hdev->ll_open_lock);
> >       kref_init(&hdev->ref);
> >
> > +#ifdef CONFIG_HID_BATTERY_STRENGTH
> > +     INIT_LIST_HEAD(&hdev->batteries);
> > +#endif
> > +
> >       ret = hid_bpf_device_init(hdev);
> >       if (ret)
> >               goto out_err;
> > diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
> > index 0e71efea9da3..9d0be3d4ce04 100644
> > --- a/drivers/hid/hid-input.c
> > +++ b/drivers/hid/hid-input.c
> > @@ -520,14 +520,20 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
> >       unsigned quirks;
> >       s32 min, max;
> >       int error;
> > +     int battery_num = 0;
> >
> > -     if (dev->battery)
> > -             return 0;       /* already initialized? */
> > +     /* Check if battery with this report_id already exists */
> > +     list_for_each_entry(bat, &dev->batteries, list) {
> > +             if (bat->report_id == field->report->id)
> > +                     return 0;       /* already initialized */
>
> I wonder if we should not make this test stick out a little bit more.
>
> Something like "get_battery(field)" which would return a battery if the
> field matches. This way, if we ever encounter multiple batteries
> reported on the same report ID, we can always split them by logical
> collection, physical, or something else.
>
I'll add that, and test it in my custom fw.

> > +             battery_num++;
> > +     }
> >
> >       quirks = find_battery_quirk(dev);
> >
> > -     hid_dbg(dev, "device %x:%x:%x %d quirks %d\n",
> > -             dev->bus, dev->vendor, dev->product, dev->version, quirks);
> > +     hid_dbg(dev, "device %x:%x:%x %d quirks %d report_id %d\n",
> > +             dev->bus, dev->vendor, dev->product, dev->version, quirks,
> > +             field->report->id);
> >
> >       if (quirks & HID_BATTERY_QUIRK_IGNORE)
> >               return 0;
> > @@ -542,9 +548,17 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
> >               goto err_free_bat;
> >       }
> >
> > -     psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery",
> > -                                strlen(dev->uniq) ?
> > -                                     dev->uniq : dev_name(&dev->dev));
> > +     /* Create unique name for each battery based on report ID */
> > +     if (battery_num == 0) {
> > +             psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery",
> > +                                        strlen(dev->uniq) ?
> > +                                             dev->uniq : dev_name(&dev->dev));
> > +     } else {
> > +             psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery-%d",
> > +                                        strlen(dev->uniq) ?
> > +                                             dev->uniq : dev_name(&dev->dev),
> > +                                        battery_num);
> > +     }
>
> Not sure how much conservative you need to be here, but I would prefer
> we stick to the same naming pattern whether this is the first or second
> battery.
>
Agree completely, honestly that was the ugly bit for me, and the only
reason I keep that was for 'backwards compatibility' but now I know
better, and Ill made those consistent.

> >       if (!psy_desc->name) {
> >               error = -ENOMEM;
> >               goto err_free_desc;
> > @@ -597,15 +611,23 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
> >
> >       power_supply_powers(bat->ps, &dev->dev);
> >
> > -     /* Maintain legacy single battery fields for backward compatibility */
> > -     dev->battery = bat->ps;
> > -     dev->battery_min = bat->min;
> > -     dev->battery_max = bat->max;
> > -     dev->battery_report_type = bat->report_type;
> > -     dev->battery_report_id = bat->report_id;
> > -     dev->battery_charge_status = bat->charge_status;
> > -     dev->battery_status = bat->status;
> > -     dev->battery_avoid_query = bat->avoid_query;
> > +     list_add_tail(&bat->list, &dev->batteries);
> > +
> > +     /*
> > +      * The legacy single battery API is preserved by exposing the first
> > +      * discovered battery. Systems relying on a single battery view maintain
> > +      * unchanged behavior.
> > +      */
> > +     if (battery_num == 0) {
> > +             dev->battery = bat->ps;
> > +             dev->battery_min = bat->min;
> > +             dev->battery_max = bat->max;
> > +             dev->battery_report_type = bat->report_type;
> > +             dev->battery_report_id = bat->report_id;
> > +             dev->battery_charge_status = bat->charge_status;
> > +             dev->battery_status = bat->status;
> > +             dev->battery_avoid_query = bat->avoid_query;
> > +     }
>
> Again, why keeping the old fields? Are they used anywhere?
>
Ack, and updating those on v3.

> >
> >       return 0;
> >
> > @@ -620,21 +642,33 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
> >
> >  static void hidinput_cleanup_battery(struct hid_device *dev)
> >  {
> > -     struct hid_battery *bat;
> > +     struct hid_battery *bat, *next;
> >       const struct power_supply_desc *psy_desc;
> >
> > -     if (!dev->battery)
> > -             return;
> > +     list_for_each_entry_safe(bat, next, &dev->batteries, list) {
> > +             psy_desc = bat->ps->desc;
> > +             power_supply_unregister(bat->ps);
> > +             kfree(psy_desc->name);
> > +             kfree(psy_desc);
> > +             list_del(&bat->list);
> > +             kfree(bat);
> > +     }
>
> Really, switching to devm the batteries would greatly help not making
> mistakes here.
>
> >
> > -     bat = power_supply_get_drvdata(dev->battery);
> > -     psy_desc = dev->battery->desc;
> > -     power_supply_unregister(dev->battery);
> > -     kfree(psy_desc->name);
> > -     kfree(psy_desc);
> > -     kfree(bat);
> >       dev->battery = NULL;
> >  }
> >
> > +static struct hid_battery *hidinput_find_battery(struct hid_device *dev,
> > +                                              int report_id)
> > +{
> > +     struct hid_battery *bat;
> > +
> > +     list_for_each_entry(bat, &dev->batteries, list) {
> > +             if (bat->report_id == report_id)
> > +                     return bat;
> > +     }
> > +     return NULL;
> > +}
>
> Oh, so you already have this function. So why not making use of it in
> hidinput_setup_battery()?
>
"backwards compatibility" lol, but yeah in v2 I implemented it
half-way, in v3 I'll make a proper use of it with the full list
conversion.

> > +
> >  static bool hidinput_update_battery_charge_status(struct hid_battery *bat,
> >                                                 unsigned int usage, int value)
> >  {
> > @@ -652,17 +686,16 @@ static bool hidinput_update_battery_charge_status(struct hid_battery *bat,
> >       return false;
> >  }
> >
> > -static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
> > -                                 int value)
> > +static void hidinput_update_battery(struct hid_device *dev, int report_id,
> > +                                 unsigned int usage, int value)
> >  {
> >       struct hid_battery *bat;
> >       int capacity;
> >
> > -     if (!dev->battery)
> > +     bat = hidinput_find_battery(dev, report_id);
> > +     if (!bat)
> >               return;
> >
> > -     bat = power_supply_get_drvdata(dev->battery);
> > -
> >       if (hidinput_update_battery_charge_status(bat, usage, value)) {
> >               power_supply_changed(bat->ps);
> >               return;
> > @@ -705,8 +738,8 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
> >  {
> >  }
> >
> > -static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
> > -                                 int value)
> > +static void hidinput_update_battery(struct hid_device *dev, int report_id,
> > +                                 unsigned int usage, int value)
>
> Why not hidinput_update_battery(struct hid_battery *bat, unsigned int usage, int value)?
>
I see, this is much more elegant, as I can avoid the lookup inside the
funcion that way, let me use that on the v3.

> >  {
> >  }
> >  #endif       /* CONFIG_HID_BATTERY_STRENGTH */
> > @@ -1574,7 +1607,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
> >               return;
> >
> >       if (usage->type == EV_PWR) {
> > -             hidinput_update_battery(hid, usage->hid, value);
> > +             hidinput_update_battery(hid, report->id, usage->hid, value);
>
> With the suggested change in hidinput_update_battery, first query the
> battery and then change it.
>
Ack
> >               return;
> >       }
> >
> > diff --git a/include/linux/hid.h b/include/linux/hid.h
> > index 63422130de20..a6e36835fb3c 100644
> > --- a/include/linux/hid.h
> > +++ b/include/linux/hid.h
> > @@ -700,8 +700,16 @@ struct hid_device {
> >  #ifdef CONFIG_HID_BATTERY_STRENGTH
> >       /*
> >        * Power supply information for HID devices which report
> > -      * battery strength. power_supply was successfully registered if
> > -      * battery is non-NULL.
> > +      * battery strength. Each battery is tracked separately in the
> > +      * batteries list.
> > +      */
> > +     struct list_head batteries;             /* List of hid_battery structures */
> > +
> > +     /*
> > +      * Legacy single battery support - kept for backwards compatibility.
> > +      * Points to the first battery in the list if any exists.
> > +      * power_supply was successfully registered if battery is non-NULL.
> > +      * DEPRECATED: New code should iterate through batteries list instead.
> >        */
> >       struct power_supply *battery;
>
> Nah. hid_device is pure internal interface. So change all the users if
> needed, but don't keep something around in the hope that others will do
> the work for you.
>
> For references there has been a very long discussion with Linus about
> API changes, and the result was that any API change that introduced a
> duplicate API was probably a bad design ;)
>
Ack, and that was the knowledge that I was missing and why I stuck to
much to making it backwards compatible, thanks for that really.

> Cheers,
> Benjamin
>
> >       __s32 battery_capacity;
> > --
> > 2.51.1
> >
>

Best,
Lucas Zampieri


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ