linux icon indicating copy to clipboard operation
linux copied to clipboard

Raspberry Pi Zero in HID Gadget Mode Cannot Wake Host Computer

Open zwhitchcox opened this issue 5 years ago • 38 comments

Describe the bug A raspberry pi Zero in HID gadget mode cannot wake host computer.

To reproduce Enable dwc2 driver and reboot:

 echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
sudo shutdown -r now

Load modules:

sudo modprobe libcomposite dwc2

Add keyboard to ConfigFS:

#!/bin/bash
# this is a stripped down version of https://github.com/ckuethe/usbarmory/wiki/USB-Gadgets - I don't claim any rights

modprobe libcomposite
cd /sys/kernel/config/usb_gadget/
mkdir -p g1
cd g1
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "girst" > strings/0x409/manufacturer 
echo "Hardpass" > strings/0x409/product
N="usb0"
mkdir -p functions/hid.$N
echo 1 > functions/hid.usb0/protocol
echo 1 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc
C=1
mkdir -p configs/c.$C/strings/0x409
echo "Config $C: ECM network" > configs/c.$C/strings/0x409/configuration 
echo 250 > configs/c.$C/MaxPower 
ln -s functions/hid.$N configs/c.$C/
ls /sys/class/udc > UDC

Now, output keys to /dev/hidg0

sudo su
echo -ne "\0\0\x4\0\0\0\0\0" > /dev/hidg0 #press the A-button
echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0 #release all keys

When you are plugged in to a host computer that is on, it will output the keys. That works fine.

Next suspend the host computer, and try the same thing:

sudo su
echo -ne "\0\0\x4\0\0\0\0\0" > /dev/hidg0 #press the A-button
echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0 #release all keys

You get an error:

bash: echo: write error: Resource temporarily unavailable

Per the kernel gadget documentation, this the remote wakeup functionality is supposed to be handled by the kernel gadget driver.

Expected behaviour When you send keys to the host computer, the gadget driver should send the wakeup signal to the host computer, instead of just failing.

Actual behaviour As mentioned previously, you can't write to the file

System

raspinfo
System Information
------------------

Raspberry Pi Zero W Rev 1.1

zwhitchcox avatar Nov 28 '20 22:11 zwhitchcox

Have you found a solution for this? Thanks!

crotoc avatar Jul 08 '21 04:07 crotoc

I have run into the same issue a few days ago ... but still after hours of research I couldn't get it working :(

ThomasLeister avatar Jul 08 '21 13:07 ThomasLeister

I'm fixing it now but I need some information from RPi devs.

@pelwell I'm sorry to bother you, but it seems that dwc2 has a problem with correctly determining the capabilities of the controller (or maybe not). dwc2 considers that the flag hw->hibernation = !!(hwcfg4 & GHWCFG4_HIBER) is 0 on RPi. Is it correct? I tried to force the parameter p->power = DWC2_POWER_DOWN_PARAM_HIBERNATION here and apparently everything works, I was able to wakeup the host (with some other improvements, but the key point was this flag). Is it really 0 or is it actually supported on broadcom and it's lying?

PS: Maybe related with #3151

mdevaev avatar Aug 03 '21 05:08 mdevaev

I used the USB protocol analyzer to make sure that it works. Yes, the host really switch the interface in suspend and then using dwc2_gadget_exit_hibernation(*, 1, 0), host immediately wakeups. On the other hand, do I understand correctly the purpose of this function that it should be called by RPi if RPi wants to wakeup the host? The description says that it is initiated by the host, but it has a rem_wakeup parameter that indicates if it is initiated by the device (RPi).

I also found that this code is sufficient to make wakeup work with echo 1 > /sys/devices/platform/soc/fe980000.usb/udc/fe980000.usb/srp. This works in a similar way in dwc3.

The second question is related to this: should I send a wakeup signal using dwc2_hsotg_wakeup and then wait for this event to be processed using dwc2_gadget_exit_hibernation as a callback?

static int dwc2_hsotg_wakeup(struct usb_gadget *gadget)
{
        struct dwc2_hsotg *hsotg = to_hsotg(gadget);
        struct dwc2_dregs_backup *dr = &hsotg->dr_backup;
        u32 dctl;
        unsigned long flags;

        spin_lock_irqsave(&hsotg->lock, flags);

        dwc2_writel(hsotg, dr->dctl | DCTL_RMTWKUPSIG, DCTL);
        mdelay(12);
        dctl = dwc2_readl(hsotg, DCTL);
        dctl &= ~DCTL_RMTWKUPSIG;
        dwc2_writel(hsotg, dctl, DCTL);
        spin_unlock_irqrestore(&hsotg->lock, flags);

        return 0;
}

static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
        .get_frame      = dwc2_hsotg_gadget_getframe,
        .set_selfpowered        = dwc2_hsotg_set_selfpowered,
        .udc_start              = dwc2_hsotg_udc_start,
        .udc_stop               = dwc2_hsotg_udc_stop,
        .pullup                 = dwc2_hsotg_pullup,
        .vbus_session           = dwc2_hsotg_vbus_session,
        .vbus_draw              = dwc2_hsotg_vbus_draw,
        .wakeup                 = dwc2_hsotg_wakeup, // <------------------------------------------------
};

mdevaev avatar Aug 03 '21 05:08 mdevaev

Can not understand what you described. Don't know how to help as well. Sorry!

crotoc avatar Aug 03 '21 06:08 crotoc

Interesting results. If I force the hibernate flag, then everything is fine for a while, but after 15 minutes the kernel starts to freeze. I probably shouldn't do this.

The second approach - my naive implementation of dwc2_hsotg_wakeup() - gives an interesting effect. It is able to wake up the host, but after that the hid gadget stops working until I reconnect the USB (but it can still send wakeup signal). I suppose this is due to the fact that I do not reset some other flags.

mdevaev avatar Aug 03 '21 08:08 mdevaev

Okay, I'm stupid. dr->dctl has invalid value in dwc2_hsotg_wakeup() when hibernation=0 (not forced to 1). I'll try to use dwc2_set_bit/dwc2_cleat_bit later.

But the question remains why the kernel freezes when hibernation=1.

mdevaev avatar Aug 03 '21 09:08 mdevaev

dwc2_set_bit/dwc2_cleat_bit is not working. Same result.

mdevaev avatar Aug 03 '21 09:08 mdevaev

I doubt it will be easy to obtain from Broadcom the Verilog that makes up the DWC OTG controller to discover definitively which features have been enabled, but it shouldn't be necessary. The purpose of the HWCFG<n> registers is to record those settings in a way that can be interrogated by the driver, and they are likely to have been populated automatically by the configuration software that came with the IP from the supplier.

Until you demonstrate that hibernation works properly I see no reason to not believe that it doesn't.

pelwell avatar Aug 03 '21 09:08 pelwell

@pelwell I think so. My experiments show that this partially works, but eventually leads to a freeze. It seems that either this feature is partially implemented in IP (but not enough to work), or in the case of RPi, something special is required to work properly.

What confuses me is that even without forcing this value, suspend works correctly. That is, I see how the host puts the port to suspend state, then wakes it up (I use a second keyboard for this), and the RPi USB gadget continues to work perfectly again, no matter how many times this cycle occurs.

Do I understand correctly that in the case of RPi, suspend is implemented in hardware and it does not require additional processing from the driver?

mdevaev avatar Aug 03 '21 09:08 mdevaev

I don't know.

pelwell avatar Aug 03 '21 10:08 pelwell

Okay, anyway, thanks for the information. I will continue to investigate and maybe find out something.

mdevaev avatar Aug 03 '21 10:08 mdevaev

Here's a workaround that works for me (on a pi zero w) : https://github.com/karn862/linux/commit/bbb6ccfac814f23930512ec05c0393ac1f593291. I'm calling a modified version of the dwc2_gadget_exit_hibernate function to exit the usb suspend state instead (since we're not hibernated).

I hope this can be helpful in finding a real fix, or at least be a temporary solution for anyone else missing this feature.

I've bought the pi zero w for the sole purpose of using the remote wakeup, so this is good enough for me. I haven't done any extensive testing, but it survives multiple suspend - wakeup sequences, and i can still inject keys in the hidg device afterwards.

karn862 avatar Aug 16 '21 21:08 karn862

Here's a workaround that works for me (on a pi zero w) : karn862@bbb6ccf. I'm calling a modified version of the dwc2_gadget_exit_hibernate function to exit the usb suspend state instead (since we're not hibernated).

I'm still getting the "echo: write error: Resource temporarily unavailable" message, but can confirm the workaround works for me too! I'm now able to "remote-wakeup" and unlock my host system (iPad Pro 2021) with the RPi zero. Thank you very much.

Oposum01 avatar Oct 13 '21 11:10 Oposum01

Very little information on this subject on the internet. Aside from this thread I found this post in Japanese http://www.dt8.jp/cgi-bin/adiary/adiary.cgi/0583. Looking at the Google Translate version, it seems the person was able to solve the problem.

They modified two files which I've replicated to my own fork here https://github.com/jlian/linux/compare/raspberrypi:linux:rpi-5.15.y...patch-1. Most importantly, they add dwc2_hsotg_wakeup in gadget.c just like above from @mdevaev and it looks very similar.

static int dwc2_hsotg_wakeup(struct usb_gadget *gadget)
{
       u32 dctl;
       struct dwc2_dregs_backup *dr;
       struct dwc2_hsotg *dev;
       unsigned long flags;
       dev = container_of(gadget, struct dwc2_hsotg, gadget);
       dr = &dev->dr_backup;

       spin_lock_irqsave(&dev->lock, flags);
       udelay(10);

       /* Start Remote Wakeup Signaling */
       dwc2_writel(dr->dctl | DCTL_RMTWKUPSIG, dev->regs + DCTL);
       mdelay(12);
       dctl = dwc2_readl(dev->regs + DCTL);
       dctl &= ~DCTL_RMTWKUPSIG;
       dwc2_writel(dctl, dev->regs + DCTL);

       spin_unlock_irqrestore(&dev->lock, flags);

       return 0;
}

I can't speak Japanese, don't understand most of this, nor have I tried this myself (looks like I'll have compile the kernel?). But I thought this information might be helpful to folks on this thread 😅.

jlian avatar Jul 30 '22 22:07 jlian

@jlian It will not work :) In 5.15 dwc2 started performing clock gating and it broke the patch. The good news is that I figured out how to fix it the right way: https://github.com/pikvm/packages/blob/master/packages/linux-rpi-pikvm/1003-remote-wakeup.patch

mdevaev avatar Jul 31 '22 07:07 mdevaev

Oh wow, I'm glad that I made the comment to learn about the actual fix!

@mdevaev is this also fixed in the upstream Linux kernel? Do you have any pointers on how I might apply your patch if I'm using https://github.com/homebridge/homebridge-raspbian-image?

jlian avatar Jul 31 '22 23:07 jlian

This patch is suitable for upstream 5.15. But I haven't used homebridge since I'm building kernels for Arch.

mdevaev avatar Aug 01 '22 08:08 mdevaev

@mdevaev I applied your patch, compiled the kernel, and put onto my Raspberry Pi 4 (notes https://github.com/jlian/linux-kernel-cross-compile). Unfortunately it didn't seem to fix it for me. I'm pretty (?) sure that I did the patching/compilation correctly, but it's my first time doing so and I'm not even sure how to check that my Pi is using the patched kernel or not.

Below command writes "whatup" if the computer is awake. With a sleeping computer it errors out like before.

pi@homebridge:~ $ echo -ne "\0\0\x1a\x0b\x04\x17\x18\x13" > /dev/hidg0 &&   echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0
-bash: echo: write error: Resource temporarily unavailable

EDIT: I also did echo 0xa0 > configs/c.1/bmAttributes following this post https://www.rmedgar.com/blog/using-rpi-zero-as-keyboard-setup-and-device-definition/ and still it doesn't fix it. I checked that it's applied by

pi@homebridge:~ $ cat /sys/kernel/config/usb_gadget/g1/configs/c.1/bmAttributes 
0xa0

jlian avatar Aug 04 '22 04:08 jlian

You need to enable wakeup_on_write in the driver: echo 1 > /sys/kernel/config/usb_gadget/GADGET/functions/hid.usb0/wakeup_on_write

mdevaev avatar Aug 04 '22 08:08 mdevaev

Whoa! It worked! Thanks so much @mdevaev ! I hope the patch gets included in the official kernel soon 🥇

jlian avatar Aug 04 '22 09:08 jlian

Ur welcome. I haven't sent it yet as it slightly duplicates the existing code. When I have time, I will try to put it in order. The good news is that I will be maintaining it in my repository anyway.

mdevaev avatar Aug 04 '22 09:08 mdevaev

@mdevaev - a bit off-topic reply here, but: awesome project - a KVM from a Raspberry Pi!

seamusdemora avatar Aug 04 '22 09:08 seamusdemora

Thanks)

mdevaev avatar Aug 04 '22 12:08 mdevaev

Hey guys, thanks for the great solution. Can this be applied to Pi zero?

crotoc avatar Aug 04 '22 12:08 crotoc

Patch - yes. PiKVM - you need zero2 at least.

mdevaev avatar Aug 04 '22 13:08 mdevaev

Patch - yes. PiKVM - you need zero2 at least.

Thanks! What I want is connecting pi 0 to usb and acts as a keyboard. Then connect to wifi. I would like to connect it through ssh and wake up my computer. Can you tell me whether this is able to work? :)

crotoc avatar Aug 04 '22 13:08 crotoc

Yea, why not.

mdevaev avatar Aug 04 '22 14:08 mdevaev

Awesome! Unluckily, zero 2 wifi is out of stock everywhere..... I returned mine because it didn't work when I found this thread one year ago

crotoc avatar Aug 04 '22 14:08 crotoc

@mdevaev GIve us a poke when your final patch makes it upstream and we'll back-port it.

pelwell avatar Aug 08 '22 14:08 pelwell