From: Vicki Pfau <vi@endrift.com>
To: Silvan Jegen <s.jegen@gmail.com>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>,
Jiri Kosina <jikos@kernel.org>,
Benjamin Tissoires <bentiss@kernel.org>,
linux-input@vger.kernel.org
Subject: Re: [PATCH v2 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
Date: Mon, 13 Apr 2026 18:51:36 -0700 [thread overview]
Message-ID: <092fa3e7-da59-4b3a-b3e4-c412e391fd3d@endrift.com> (raw)
In-Reply-To: <3D95AXVJ22C7J.26Z5DELLJGZTA@homearch.localdomain>
On 4/11/26 8:29 AM, Silvan Jegen wrote:
> Vicki Pfau <vi@endrift.com> wrote:
>> On 4/10/26 12:44, Silvan Jegen wrote:
>>> Vicki Pfau <vi@endrift.com> wrote:
>>>> Replies inline
>>>>
>>>> On 4/8/26 12:51, Silvan Jegen wrote:
>>>>> Heyhey!
>>>>>
>>>>> Vicki Pfau <vi@endrift.com> wrote:
>>>>>> Hi,
>>>>>>
>>>>>> Replies inline
>>>>>>
>>>>>> On 4/2/26 12:09 PM, Silvan Jegen wrote:
>>>>>>> Hi
>>>>>>>
>>>>>>> Thanks for the patch!
>>>>>>>
>>>>>>> Just some comments and questions inline below.
>>>>>>>
>>>>>>> Vicki Pfau <vi@endrift.com> wrote:
>>>>>>>>
>>>>>>>> [...]
>>>>>>>>
>>>>>>>> +
>>>>>>>> +static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
>>>>>>>> +{
>>>>>>>> + __le32 format_id = __cpu_to_le32(fmt);
>>>>>>>> +
>>>>>>>> + if (!ns2->cfg)
>>>>>>>> + return -ENOTCONN;
>>>>>>>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
>>>>>>>> + &format_id, sizeof(format_id),
>>>>>>>> + ns2->cfg);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int switch2_init_controller(struct switch2_controller *ns2)
>>>>>>>
>>>>>>> This is now a recursive call while in v1 it wasn't. I think I preferred
>>>>>>> the non-recursive version as there was one place where init_step
>>>>>>> state was changed while now I am not sure where it happens (and whether
>>>>>>> there is a code path where we end up in an infinite recursion)
>>>>>>>
>>>>>>> What is the advantage of the recursive version compared to the
>>>>>>> non-recursive one?
>>>>> >
>>>>>>
>>>>>> The old version incremented the step regardless of whether or not it
>>>>>> could confirm it had happened. Since the confirmation is now handled
>>>>>> with an external step, calling into switch2_init_step_done, the loop
>>>>>> condition would become somewhat complicated.
>>>>>> I replaced it with explicit tail calls since that make the
>>>>>> control flow simplier, and it is always matched with a call to
>>>>>> switch2_init_step_done to ensure that the state is always advanced. As
>>>>>
>>>>> From what I can tell switch2_init_step_done currently only advances
>>>>> the state if the current state is the expected one. This seems fine,
>>>>> but it also means that if the state is not the expected one, the
>>>>> state is not advanced and the recursive call continues anyway (in the
>>>>> NS2_INIT_READ_USER_SECONDARY_CALIB case, for example). I assume this
>>>>> should never happen but if we end up in this case for some reason we
>>>>> will recurse forever.
>>>>
>>>> That's correct, and the only way it would happen forever is if there's a
>>>> bug. The same would be true in a loop version if it doesn't advance the
>>>> state properly either, fwiw, which happened during development of this
>>>> version. Regardless, I can reduce the chance of introducing such a bug
>>>> by passing ns2->init_step instead of a constant, so I'll make that
>>>> change in v4.
>>>>
>>>>>
>>>>> The same case also potentially calls switch2_read_flash and it isn't
>>>>> clear to me if this means that the initialisation is done (as there
>>>>> is no switch2_init_step_done call and we are not in the FINISH state
>>>>> either). There is also the possibility of switch2_read_flash calling
>>>>> switch2_init_controller again, which one then has to check ... (note
>>>>> that this is not the case here though)
>>>>
>>>> switch2_handle_flash_read will advance the state once it's verified that
>>>> the read actually happened. If the step failed for whatever reason, this
>>>> same codepath will retry the specific read, as the caller
>>>> (switch2_receive_command) will always call into switch2_init_controller
>>>> if setup isn't done. This is how the retry logic works.
>>>
>>> Ah, so the call chain looks something like the below?
>>>
>>> switch2_read_flash->
>>> switch2_usb_send_cmd->
>>> switch2_usb_message_in_work (?)->
>>> switch2_receive_command->
>>> switch2_handle_flash_read->
>>> switch2_init_step_done
>>
>> Yes, and then switch2_init_controller is called again at the end of
>> switch2_receive_command
>>>
>>>>
>>>>>
>>>>> To me it seems like it would be clearer to do a `ns2->init_step++` and
>>>>> then `continue` to make the progress of the state more visible and to
>>>>> do an explicit `break` when we are supposed to stop the initialisation.
>>>>>
>>>>
>>>> The problem with the loop approach, in my opinion is due to the fact
>>>> that the loop is the *exception*, not the rule. The loop idiom makes it
>>>> look like a loop is expected. Further the ns2->init_step++ in the
>>>> previous version means that the verification does not occur, so in the
>>>> case of any sort of failure it'll plow ahead anyway instead of retrying.
>>>> The point of this approach is to avoid that.
>>>
>>> In my mind a while-loop like you mentioned it above would make the state
>>> changes more obvious (since they could all be done in the loop body), while
>>> still allowing for retries. Something like the below, perhaps (untested).
>>>
>>> while (ns2->init_step < NS2_INIT_DONE) {
>>> switch (ns2->init_step) {
>>> ...
>>> case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
>>> if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
>>> ns->init_step++
>>> continue;
>>> }
>>>
>>> ret = switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
>>> NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
>>> if (ret) {
>>> // if it makes sense to retry here
>>> continue;
>>> }
>>>
>>> ns->init_step++
>>> break;
>>>
>>> case ...
>>> }
>>> }
>>>
>>
>> This can't be done because the process is fundamentally asynchronous.
>> We'd have to block on waiting for a reply to the USB packet, which is
>> not a good idea. This is why switch2_receive_command calls into
>> switch2_init_controller at the end: it's resuming where it left off.
>
> Ah, that wasn't clear to me either.
>
> I assume having the code wait for the reply is not allowed because
> otherwise it would stall the whole bootup process (or will there be some
> sort of dedicated Kthread for this)?
I'm not sure there is a synchronous USB API at all, but if there is it should really only be used when absolutely necessary. If you can get away without blocking, do.
>
> Are you aware of any documentation where I can read up on how the probing
> of USB HID devices work in the Linux Kernel?
This has nothing to do with HID, actually. The Switch 2 controllers have a proprietary side-channel interface that we need to poke at here to get it to do anything. It's called cfg in the code and over USB it uses a bulk interface, which is unusual. Presumably because the side-channel doesn't care about latency, whereas the HID interface does. Something similar happens over bluetooth where they're accessed through disparate attributes. The latter isn't handled here yet, though.
>
> Thanks for the help!
>
>> Each of those returns is a step that interacts with the hardware, and we
>> need to wait for the hardware to reply.
>>
>> There is a potential weird interaction here whereby if we get an
>> unprompted command and/or reply from the controller it will retry a step
>> before it gets a reply for it, but in practice this doesn't happen. The
>> controllers, as far as we know, only reply and never initiate any
>> commands. Furthermore, all of these steps are also idempotent, so it's
>> not a big deal of they get repeated erroneously.
>
> Could there be another reply incoming while the driver is still processing
> the previous one? I assume at least at probing time that shouldn't be
> the case. I wouldn't expect an USB HID device to send unsolicited replies
> in general ...
Replies only come in one at a time. Unless we send multiple messages without waiting for a reply first, which this code is specifically designed *not* to do, I don't think it will happen in practice.
>
> Cheers,
> Silvan
next prev parent reply other threads:[~2026-04-14 1:51 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-18 3:08 [PATCH v2 0/3] HID: nintendo: Add preliminary Switch 2 controller driver Vicki Pfau
2026-03-18 3:08 ` [PATCH v2 1/3] " Vicki Pfau
2026-04-02 19:09 ` Silvan Jegen
2026-04-03 1:17 ` Vicki Pfau
2026-04-08 19:51 ` Silvan Jegen
2026-04-10 2:17 ` Vicki Pfau
2026-04-10 19:44 ` Silvan Jegen
2026-04-10 22:30 ` Vicki Pfau
2026-04-11 15:29 ` Silvan Jegen
2026-04-14 1:51 ` Vicki Pfau [this message]
2026-03-18 3:08 ` [PATCH v2 2/3] HID: nintendo: Add rumble support for Switch 2 controllers Vicki Pfau
2026-04-02 19:11 ` Silvan Jegen
2026-03-18 3:08 ` [PATCH v2 3/3] HID: nintendo: Add unified report format support Vicki Pfau
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=092fa3e7-da59-4b3a-b3e4-c412e391fd3d@endrift.com \
--to=vi@endrift.com \
--cc=bentiss@kernel.org \
--cc=dmitry.torokhov@gmail.com \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=s.jegen@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox