Skip to content

Instantly share code, notes, and snippets.

@dafta
Last active April 29, 2024 23:47
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dafta/0aadeba3aa8bcbbc8b92a233977571ed to your computer and use it in GitHub Desktop.
Save dafta/0aadeba3aa8bcbbc8b92a233977571ed to your computer and use it in GitHub Desktop.
Steam Deck USB Ethernet
#!/bin/sh
if [ "$UID" -ne 0 ]; then
echo "This script needs to be executed as root"
exit 1
fi
vendor_id="0x3000" # Valve
product_id="0x28DE"
serial_number="$(dmidecode -s system-serial-number)" # The Steam Deck's serial number
manufacturer="Valve" # Manufacturer
product="Steam Deck" # Product
device="0x1004" # Device version
usb_version="0x0200" # USB 2.0
device_class="2" # Communications
cfg1="CDC" # Config 1 description
cfg2="RNDIS" # Config 2 description
power=250 # Max power
dev_mac1="42:61:64:55:53:42"
host_mac1="48:6f:73:74:50:43"
dev_mac2="42:61:64:55:53:44"
host_mac2="48:6f:73:74:50:45"
ms_vendor_code="0xcd" # Microsoft
ms_qw_sign="MSFT100" # Microsoft
ms_compat_id="RNDIS" # Matches Windows RNDIS drivers
ms_subcompat_id="5162001" # Matches Windows RNDIS 6.0 driver
cdc_mode="ecm" # Which CDC gadget to use
start_rndis=true # Whether to start the Microsoft RNDIS gadget
while getopts "ncerR" option ${@:2}; do
case "${option}" in
"n")
cdc_mode=ncm
;;
"c")
cdc_mode=ecm
;;
"e")
cdc_mode=eem
;;
"r")
start_rndis=true
;;
"R")
start_rndis=false
;;
esac
done
case "$1" in
start)
# Create the networkd config file for the USB interface
cat << EOF > /etc/systemd/network/usb0.network
[Match]
Name=usb0
[Network]
Address=192.168.100.1/24
DHCPServer=true
IPMasquerade=ipv4
[DHCPServer]
PoolOffset=100
PoolSize=20
EmitDNS=yes
DNS=8.8.8.8
EOF
cat << EOF > /etc/systemd/network/usb1.network
[Match]
Name=usb1
[Network]
Address=192.168.101.1/24
DHCPServer=true
IPMasquerade=ipv4
[DHCPServer]
PoolOffset=100
PoolSize=20
EmitDNS=yes
DNS=8.8.8.8
EOF
# Start networkd
systemctl start systemd-networkd
# Load the drivers
modprobe libcomposite
# Create the gadget
mkdir /sys/kernel/config/usb_gadget/g.1
cd /sys/kernel/config/usb_gadget/g.1
# Specify the vendor and product ID
echo "${vendor_id}" > idVendor
echo "${product_id}" > idProduct
# Create the gadget configuration
mkdir configs/c.1
# Create the strings directories
mkdir strings/0x409
mkdir configs/c.1/strings/0x409
# Specify the serial number, manufacturer, and product strings
echo "${serial_number}" > strings/0x409/serialnumber
echo "${manufacturer}" > strings/0x409/manufacturer
echo "${product}" > strings/0x409/product
# Specify the device version, USB specification, and device class
echo "${device}" > bcdDevice
echo "${usb_version}" > bcdUSB
echo "${device_class}" > bDeviceClass
# Set the configuration description and power
echo "${cfg1}" > configs/c.1/strings/0x409/configuration
echo "${power}" > configs/c.1/MaxPower
# Create the gadget function
mkdir functions/${cdc_mode}.0
# Set the MAC addresses of the gadget
echo "${host_mac1}" > functions/${cdc_mode}.0/host_addr
echo "${dev_mac1}" > functions/${cdc_mode}.0/dev_addr
# Start RNDIS if enabled
if [ "${start_rndis}" = true ]; then
# Create the gadget configuration
mkdir configs/c.2
# Create the strings directories
mkdir configs/c.2/strings/0x409
# Specify the configuration description and power
echo "${cfg2}" > configs/c.2/strings/0x409/configuration
echo "${power}" > configs/c.2/MaxPower
# Set some Microsoft specific configuration
echo "1" > os_desc/use
echo "${ms_vendor_code}" > os_desc/b_vendor_code
echo "${ms_qw_sign}" > os_desc/qw_sign
# Create the gadget function
mkdir functions/rndis.0
# Set the MAC addresses of the gadget
echo "${host_mac2}" > functions/rndis.0/host_addr
echo "${dev_mac2}" > functions/rndis.0/dev_addr
# Set the RNDIS driver version
echo "${ms_compat_id}" > functions/rndis.0/os_desc/interface.rndis/compatible_id
echo "${ms_subcompat_id}" > functions/rndis.0/os_desc/interface.rndis/sub_compatible_id
fi
# Associate the CDC function with its configuration
ln -s functions/${cdc_mode}.0 configs/c.1/
# Associate the RNDIS function with its configuration
if [ "${start_rndis}" = true ]; then
ln -s functions/rndis.0 configs/c.2
ln -s configs/c.2 os_desc
fi
# Enable the gadget
ls /sys/class/udc > UDC
;;
stop)
# Disable the gadget
cd /sys/kernel/config/usb_gadget/g.1
echo "" > UDC
# Remove functions from the configuration
rm configs/c.1/ncm.0 2> /dev/null
rm configs/c.1/ecm.0 2> /dev/null
rm configs/c.1/eem.0 2> /dev/null
rm configs/c.2/rndis.0 2> /dev/null
# Remove the strings directories in configurations
rmdir configs/c.1/strings/0x409
rmdir configs/c.2/strings/0x409 2> /dev/null
# Remove the configurations
rmdir configs/c.1
rm os_desc/c.2 2> /dev/null
rmdir configs/c.2 2> /dev/null
# Remove the functions
rmdir functions/ncm.0 2> /dev/null
rmdir functions/ecm.0 2> /dev/null
rmdir functions/eem.0 2> /dev/null
rmdir functions/rndis.0 2> /dev/null
# Remove the strings directories in the gadget
rmdir strings/0x409
# Delete the gadget
cd ..
rmdir g.1
# Unload the drivers
cd ../../
modprobe -r usb_f_ncm
modprobe -r usb_f_ecm
modprobe -r usb_f_eem
modprobe -r usb_f_rndis
modprobe -r libcomposite
# Stop networkd
systemctl stop systemd-networkd 2> /dev/null
# Remove the networkd config files for USB interfaces
rm /etc/systemd/network/usb0.network
rm /etc/systemd/network/usb1.network
;;
*)
echo "Usage:"
echo -e "\t./usb-ether.sh start\tStarts the USB ethernet"
echo -e "\t\t-n\tUse the CDC-NCM USB Ethernet driver (for OSX and iOS)"
echo -e "\t\t-c\tUse the CDC-ECM USB Ethernet driver (default)"
echo -e "\t\t-e\tUse the CDC-EEM USB Ethernet driver"
echo -e "\t\t-r\tEnable the RNDIS USB Ethernet driver for Windows (default)"
echo -e "\t\t-R\tDisable the RNDIS USB Ethernet driver for Windows"
echo -e "\t./usb-ether.sh stop - Stops the USB ethernet"
;;
esac
@dafta
Copy link
Author

dafta commented Feb 5, 2023

I can update the script to add a switch to enable or disable the DHCP server. This also requires the other computer to run a DHCP server, or to set up a static IP address manually on both devices.

@cheater
Copy link

cheater commented Feb 5, 2023

would make sense. but it would be nice if you could make sure to let people know something like the following: "This option is what you use when you want to connect your Steam Deck to another computer that has internet, in order to access that computer's internet. That computer will have to run a DHCP server and it will have to share its internet with the Steam Deck."

@dafta
Copy link
Author

dafta commented Feb 5, 2023

Yeah, definitely. I'd also make the DHCPServer run by default on the Deck, same as RNDIS and ECM already do if you just do ./usb-ether start

@cheater
Copy link

cheater commented Feb 5, 2023

you could make the flag something like --client (idk if getopt supports long options now)... maybe -c ...

@cheater
Copy link

cheater commented Feb 5, 2023

oh, i guess -c is already being used...

@cheater
Copy link

cheater commented Feb 5, 2023

OK, so, i've been trying this thing some more, and it looks like RNDIS will outright not work.

  1. it seems like sharing internet to an RNDIS connection in Windows is somehow broken. Not surprised honestly if that were the case. Sometimes it just doesn't run a dhcp server on Windows, and the Deck doesn't get internet. Currently I tried multiple times and now i'm just straight up unable to get internet on the deck like this. However, I also have a dock with ethernet for the deck, and an ethernet dongle, that I can connect to the pc. So if I go from the pc to the sd like pc -> ethernet dongle -> cat 5 -> ethernet dock -> sd, then sharing to that always works.

  2. when trying to share internet with multiple devices (I have an xbox plugged into ethernet + the deck which I'd like to connect via usb c) then you have to bridge those devices into a bridge, and share internet to that bridge. In my testing i've never been able to get internet on the Deck like this. Only the xbox got the internet. If I connect the SD via the ethernet dongle route like above, then it gets internet via a bridge too. So in that configuration both the xbox and the deck get internet at once, as well as the PC that is doing the bridging.

So it seems to me like RNDIS is just broken in windows 10. I don't know about windows 11, but I can't imagine it's better.

So for this reason, maybe you could instead figure out a way to use the Ethernet kernel gadget to emulate an ethernet interface?

https://linuxlink.timesys.com/docs/wiki/engineering/HOWTO_Use_USB_Gadget_Ethernet

This way, if I connect the deck to windows, I understand windows would see a new Ethernet connection. But it would think it's a "real deal" Ethernet connection, and not RNDIS. My guess is then windows won't have such issues sharing internet to it.

cc @parkerlreed

Edit: I take it back, the linked to page also works via RNDIS, so following the process outlined in it won't help at all.

@cheater
Copy link

cheater commented Feb 5, 2023

This might be some sort of solution. Will have to look into it when I have more time...

https://github.com/microsoft/NCM-Driver-for-Windows

@dafta
Copy link
Author

dafta commented Feb 5, 2023

Problem is that Windows only supports RNDIS, for reasons only they know. I don't know much about Windows, it might be possible to find a NCM or ECM driver, but by default, they don't support anything else.

This might help, though: https://stackoverflow.com/questions/37630403/composite-usb-cdc-gadget-doesnt-work-with-windows-hosts

@tomer-rgo
Copy link

Tried to use this for ethernet between Ipad pro to the Steam Deck and it didn't seem to work.. any ideas ?

@dafta
Copy link
Author

dafta commented Mar 1, 2023

@tomer-rgo Try using the -n flag. Only the NCM gadget works on iOS.

@YemingMeng
Copy link

YemingMeng commented Mar 1, 2023

Nothing happens after executing the script, I found the directory /sys/class/udc is empty.
Are there any pre-requirements before running the script, or is a reboot required?

And I got the following error message after running ./usb-ether.sh stop,

./usb-ether.sh: line 173: echo: write error: No such device

@dafta
Copy link
Author

dafta commented Mar 2, 2023

@YemingMeng Did you enable USB Dual-Role Device in BIOS?

@YemingMeng
Copy link

@YemingMeng Did you enable USB Dual-Role Device in BIOS?

Well, the script works as expected after enabling USB Dual-Role Device, thanks a lot.

@L1Z3
Copy link

L1Z3 commented Jul 18, 2023

This script was working for me, but after updating to SteamOS 3.5 (Main Channel) and then downgrading back to Stable (which upgraded the BIOS), it no longer works for me. I have ensured DRD is still enabled in the BIOS.

@parkerlreed
Copy link

BIOS 116 is busted for DRD. Just have to wait for an update or manaully downgrade to 115

@L1Z3
Copy link

L1Z3 commented Jul 18, 2023

Alright. I needed to upgrade the BIOS to fix another issue on my Deck, so I guess I'll just have to wait for an update.

@L1Z3
Copy link

L1Z3 commented Oct 22, 2023

I updated to BIOS 118 and it still doesn't seem to work. Any ideas?

DRD is enabled in the BIOS, but when running sudo ./usb-ether.sh stop I get:
./usb-ether.sh: line 173: echo: write error: No such device

EDIT: I tried downgrading to BIOS 115, but SteamOS auto-updated to BIOS 116. I then manually flashed BIOS 118, and now DRD works.

@MYSK-D25N
Copy link

EDIT: I tried downgrading to BIOS 115, but SteamOS auto-updated to BIOS 116. I then manually flashed BIOS 118, and now DRD works.

Sometime ago the script stopped working for me too. After downgrading BIOS from 120 to 118 today, everything works as intended.

@dafta
Copy link
Author

dafta commented Feb 14, 2024

BIOS 221 should also work.

@ArcaneNibble
Copy link

I'm not using this exact script, but in order to make gadget mode work on BIOS F7A0120 I needed the following commands:

echo -n "0000:04:00.3" > /sys/bus/pci/drivers/xhci_hcd/unbind
echo -n "0000:04:00.3" > /sys/bus/pci/drivers/dwc3-pci/bind

@dafta
Copy link
Author

dafta commented Mar 6, 2024

I'm not using this exact script, but in order to make gadget mode work on BIOS F7A0120 I needed the following commands:

echo -n "0000:04:00.3" > /sys/bus/pci/drivers/xhci_hcd/unbind
echo -n "0000:04:00.3" > /sys/bus/pci/drivers/dwc3-pci/bind

This is with DRD enabled in the BIOS?

@ArcaneNibble
Copy link

I'm not using this exact script, but in order to make gadget mode work on BIOS F7A0120 I needed the following commands:

echo -n "0000:04:00.3" > /sys/bus/pci/drivers/xhci_hcd/unbind
echo -n "0000:04:00.3" > /sys/bus/pci/drivers/dwc3-pci/bind

This is with DRD enabled in the BIOS?

Yes, the BIOS setting still has to be DRD

@dafta
Copy link
Author

dafta commented Mar 7, 2024

I'm not using this exact script, but in order to make gadget mode work on BIOS F7A0120 I needed the following commands:

echo -n "0000:04:00.3" > /sys/bus/pci/drivers/xhci_hcd/unbind
echo -n "0000:04:00.3" > /sys/bus/pci/drivers/dwc3-pci/bind

This is with DRD enabled in the BIOS?

Yes, the BIOS setting still has to be DRD

Thanks a ton for figuring this out, this will be a huge help.

@parkerlreed
Copy link

I'm not using this exact script, but in order to make gadget mode work on BIOS F7A0120 I needed the following commands:

echo -n "0000:04:00.3" > /sys/bus/pci/drivers/xhci_hcd/unbind
echo -n "0000:04:00.3" > /sys/bus/pci/drivers/dwc3-pci/bind

THANK YOU, This is tremendously helpful.

@MYSK-D25N
Copy link

The abofe gist is for sharing the internet Steam Deck has with some other device that'll be getting its internet access via the Steam Deck.

If you want the opposite, i.e. your Steam Deck using internet from another device, use this gist:

https://gist.github.com/cheater/00279e5b3dea743e14cf7bcd57f6c7fa

Basically it does two things:

  1. disables its own dhcp server
  2. instead of a fixed address, gets its ethernet address from the dhcp server run by the other computer.

Is there any way to get this working while docked? The usb-hub which I use has a USB-C port for data transfers, but upon trying it doesn't seem to work. Maybe I could point it to the port by changing something in the script, I don't really know. Would be cool if possible though.

@dafta
Copy link
Author

dafta commented Apr 25, 2024

@MYSK-D25N Most likely no. To use a dock, the USB port needs to be in host mode, while this script requires the port to be in client mode. There's no way around this, by using this script the USB port goes into client mode and requires the other end to be a PC or device in host mode.

The only way this would work is if your dock exposes a USB device controller (UDC) to the deck. You can check if you have more than one entry in /sys/class/udc, and if you do, you would echo the name of that other UDC to the file in line 167 of the script. But it's a question if the USB C port has Dual-Role Device support, and if the dock has firmware that enables that.

Good luck, and post a reply if you succeed.

@MYSK-D25N
Copy link

@dafta Thanks for the swift reply. It does seem unlikely that my dock is capable of that, there was one entry in UDC folder while nothing is plugged, and then no entries while docked.

Also looked around in dmesg output for clues, the dock shows up there as a "xHCI Host Controller" with the manufacturer as "Linux 6.1.52-valve16-1-neptune-61 xhci-hcd", despite DRD being ON. Didn't find if this can be changed somehow, probably not.

So yeah, doesn't really seem to work, but thanks for clarifying nonetheless.

@EpicestGamer
Copy link

Is there any chance of this being made into a Decky plugin like mentioned here?

@dafta
Copy link
Author

dafta commented Apr 29, 2024

I'm already working on it, but progress is slow when DRD breaks every other update by Valve.

@EpicestGamer
Copy link

Completely fair, glad to hear it. Thank you for all your hard work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment