Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add USB Ethernet Gadget toggle #933

Open
wants to merge 12 commits into
base: qml
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,11 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc ${QM_FILES})

if (WIN32)
# Adding WIN32 prevents a console window being opened on Windows
add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${HEADERS} ${DEPENDENCIES})
add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${HEADERS} ${DEPENDENCIES}
extraFiles.qrc)
else()
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} ${DEPENDENCIES})
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} ${DEPENDENCIES}
extraFiles.qrc)
endif()

set_property(TARGET ${PROJECT_NAME} PROPERTY AUTOMOC ON)
Expand Down Expand Up @@ -495,4 +497,4 @@ else()
endif()

include_directories(${CURL_INCLUDE_DIR} ${LibArchive_INCLUDE_DIR} ${LIBLZMA_INCLUDE_DIRS} ${LIBDRM_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${ZSTD_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE ${QT}::Core ${QT}::Quick ${QT}::Svg ${CURL_LIBRARIES} ${LibArchive_LIBRARIES} ${ZSTD_LIBRARIES} ${ZLIB_LIBRARIES} ${LIBLZMA_LIBRARIES} ${LIBDRM_LIBRARIES} ${ATOMIC_LIBRARY} ${EXTRALIBS})
target_link_libraries(${PROJECT_NAME} PRIVATE ${QT}::Core ${QT}::Quick ${QT}::Svg ${CURL_LIBRARIES} ${LibArchive_LIBRARIES} ${ZSTD_LIBRARIES} ${ZLIB_LIBRARIES} ${LIBLZMA_LIBRARIES} ${LIBDRM_LIBRARIES} ${ATOMIC_LIBRARY} ${EXTRALIBS})
43 changes: 31 additions & 12 deletions src/OptionsPopup.qml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Window {
property string cloudinitrun
property string cloudinitwrite
property string cloudinitnetwork
property bool deviceUsbOtgSupport: false
property bool enableEtherGadget

signal saveSettingsSignal(var settings)

Expand Down Expand Up @@ -355,6 +357,12 @@ Window {
ColumnLayout {
// Remote access tab

ImCheckBox {
id: chkUSBEther
text: qsTr("Enable USB Ethernet Gadget")
enabled: deviceUsbOtgSupport
}

ImCheckBox {
id: chkSSH
text: qsTr("Enable SSH")
Expand Down Expand Up @@ -629,6 +637,14 @@ Window {
}
}

if (imageWriter.andCapabilities("usb_otg")) {
deviceUsbOtgSupport = true
} else {
deviceUsbOtgSupport = false
// make sure it isn't disabled and selected
chkUSBEther.checked = false
}

//open()
show()
raise()
Expand Down Expand Up @@ -826,6 +842,20 @@ Window {

addCmdline("cfg80211.ieee80211_regdom="+fieldWifiCountry.editText)
}
if (chkUSBEther.checked) {
// keep parity with cli.cpp
paulober marked this conversation as resolved.
Show resolved Hide resolved
addConfig("dtoverlay=dwc2,dr_mode=peripheral")

enableEtherGadget = true;

addFirstRun("\nmv /boot/firmware/10usb.net /etc/systemd/network/10-usb.network")
addFirstRun("mv /boot/firmware/geth.cnf /etc/modprobe.d/g_ether.conf")
addFirstRun("mv /boot/firmware/gemod.cnf /etc/modules-load.d/usb-ether-gadget.conf\n")
addFirstRun("SERIAL=$(grep Serial /proc/cpuinfo | awk '{print $3}')")
addFirstRun("sed -i \"s/<serial>/$SERIAL/g\" /etc/modprobe.d/g_ether.conf")
addFirstRun("systemctl enable systemd-networkd\n")
}

if (chkLocale.checked) {
var kbdconfig = "XKBMODEL=\"pc105\"\n"
kbdconfig += "XKBLAYOUT=\""+fieldKeyboardLayout.editText+"\"\n"
Expand All @@ -851,17 +881,6 @@ Window {
addCloudInit(" layout: \"" + fieldKeyboardLayout.editText + "\"")
}

if (firstrun.length) {
firstrun = "#!/bin/bash\n\n"+"set +e\n\n"+firstrun
addFirstRun("rm -f /boot/firstrun.sh")
addFirstRun("sed -i 's| systemd.run.*||g' /boot/cmdline.txt")
addFirstRun("exit 0")
/* using systemd.run_success_action=none does not seem to have desired effect
systemd then stays at "reached target kernel command line", so use reboot instead */
//addCmdline("systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target")
// cmdline changing moved to DownloadThread::_customizeImage()
}

if (cloudinitwrite !== "") {
addCloudInit("write_files:\n"+cloudinitwrite+"\n")
}
Expand All @@ -870,7 +889,7 @@ Window {
addCloudInit("runcmd:\n"+cloudinitrun+"\n")
}

imageWriter.setImageCustomization(config, cmdline, firstrun, cloudinit, cloudinitnetwork)
imageWriter.setImageCustomization(config, cmdline, firstrun, cloudinit, cloudinitnetwork, false, enableEtherGadget)
}

function saveSettings()
Expand Down
17 changes: 14 additions & 3 deletions src/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,22 @@ int Cli::run()
{"first-run-script", "Add firstrun.sh to image", "first-run-script", ""},
{"cloudinit-userdata", "Add cloud-init user-data file to image", "cloudinit-userdata", ""},
{"cloudinit-networkconfig", "Add cloud-init network-config file to image", "cloudinit-networkconfig", ""},
{"usb-ether-gadget", "Enable USB Ethernet Gadget mode (does not support --first-run-script)"},
{"disable-eject", "Disable automatic ejection of storage media after verification"},
{"debug", "Output debug messages to console"},
{"quiet", "Only write to console on error"},
});

parser.addVersionOption();
parser.addHelpOption();
parser.addPositionalArgument("src", "Image file/URL");
parser.addPositionalArgument("dst", "Destination device");
parser.process(*_app);

const QStringList args = parser.positionalArguments();
if (args.count() != 2)
{
std::cerr << "Usage: --cli [--disable-verify] [--disable-eject] [--sha256 <expected hash> [--cache-file <cache file>]] [--first-run-script <script>] [--debug] [--quiet] <image file to write> <destination drive device>" << std::endl;
std::cerr << "Usage: --cli [--disable-verify] [--disable-eject] [--sha256 <expected hash> [--cache-file <cache file>]] [--first-run-script <script>] [--usb-ether-gadget] [--debug] [--quiet] <image file to write> <destination drive device>" << std::endl;
return 1;
}

Expand Down Expand Up @@ -148,6 +151,8 @@ int Cli::run()
}
}

bool isEtherGadgetEnabled = parser.isSet("usb-ether-gadget");

if (!parser.value("cloudinit-userdata").isEmpty())
{
QByteArray userData, networkConfig;
Expand Down Expand Up @@ -186,10 +191,15 @@ int Cli::run()
return 1;
}

_imageWriter->setImageCustomization("", "", "", userData, networkConfig);
_imageWriter->setImageCustomization("", "", "", userData, networkConfig, false, isEtherGadgetEnabled);
}
else if (!parser.value("first-run-script").isEmpty())
{
if (isEtherGadgetEnabled) {
std::cerr << "Error: the --usb-ether-gadget option is not supported when --first-run-script is used.";
return 1;
}

QByteArray firstRunScript;
QFile f(parser.value("first-run-script"));
if (!f.exists())
Expand All @@ -208,11 +218,12 @@ int Cli::run()
return 1;
}

_imageWriter->setImageCustomization("", "", firstRunScript, "", "");
_imageWriter->setImageCustomization("", "", firstRunScript, "", "", true, isEtherGadgetEnabled);
}

_imageWriter->setDst(args[1]);
_imageWriter->setVerifyEnabled(!parser.isSet("disable-verify"));
_imageWriter->setEtherGadgetEnabled(isEtherGadgetEnabled);
_imageWriter->setSetting("eject", !parser.isSet("disable-eject"));

/* Run startWrite() in event loop (otherwise calling _app->exit() on error does not work) */
Expand Down
59 changes: 55 additions & 4 deletions src/downloadthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,10 @@ void DownloadThread::setVerifyEnabled(bool verify)
_verifyEnabled = verify;
}

void DownloadThread::setEtherGadgetEnabled(bool etherGadget) {
_enableEtherGadget = etherGadget;
}

bool DownloadThread::isImage()
{
return true;
Expand Down Expand Up @@ -885,14 +889,16 @@ qint64 DownloadThread::_sectorsWritten()
return -1;
}

void DownloadThread::setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun, const QByteArray &cloudinit, const QByteArray &cloudInitNetwork, const QByteArray &initFormat)
void DownloadThread::setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun, const QByteArray &cloudinit, const QByteArray &cloudInitNetwork, const QByteArray &initFormat, const bool userDefinedFirstRun, const bool enableEtherGadget)
{
_config = config;
_cmdline = cmdline;
_firstrun = firstrun;
_cloudinit = cloudinit;
_cloudinitNetwork = cloudInitNetwork;
_initFormat = initFormat;
_userDefinedFirstRun = userDefinedFirstRun;
_enableEtherGadget = enableEtherGadget;
}

bool DownloadThread::_customizeImage()
Expand Down Expand Up @@ -968,10 +974,55 @@ bool DownloadThread::_customizeImage()
}
}

if (!_firstrun.isEmpty() && _initFormat == "systemd")
if (_enableEtherGadget) {
// load files from disk and write
QByteArray networkConfig = _fileGetContentsTrimmed("://extraFiles/10-usb.network");
fat->writeFile("10usb.net", networkConfig);
// little optimization for memory constrained systems
networkConfig.clear();

// only needed for manual config without g_ether
paulober marked this conversation as resolved.
Show resolved Hide resolved
QByteArray modprobeConf = _fileGetContentsTrimmed("://extraFiles/g_ether.conf");
fat->writeFile("geth.cnf", modprobeConf);
// little optimization for memory constrained systems
modprobeConf.clear();

QByteArray modulesConf = _fileGetContentsTrimmed("://extraFiles/usb-ether-gadget.conf");
fat->writeFile("gemod.cnf", modulesConf);
// little optimization for memory constrained systems
modulesConf.clear();

QByteArray controllScript = _fileGetContentsTrimmed("://extraFiles/rpi-usb-ether-gadget.sh");
fat->writeFile("uethc.sh", controllScript);
controllScript.clear();

// add config.txt change - \n prefix to also work if user defined config doesn't end with a LF
_config.append("\ndtoverlay=dwc2,dr_mode=peripheral\n");
_firstrun.append("\nmv /boot/firmware/10usb.net /etc/systemd/network/10-usb.network\n");
_firstrun.append("mv /boot/firmware/geth.cnf /etc/modprobe.d/g_ether.conf\n");
_firstrun.append("mv /boot/firmware/gemod.cnf /etc/modules-load.d/usb-ether-gadget.conf\n");
_firstrun.append("mv /boot/firmware/uethc.sh /usr/bin/rpi-usb-ether-gadget\n");
_firstrun.append("chmod +x /usr/bin/rpi-usb-ether-gadget\n\n");
_firstrun.append("SERIAL=$(grep Serial /proc/cpuinfo | awk '{print $3}')\n");
_firstrun.append("sed -i \"s/<serial>/$SERIAL/g\" /etc/modprobe.d/g_ether.conf\n");
_firstrun.append("systemctl enable systemd-networkd\n\n");
}

if (!_firstrun.isEmpty())
{
fat->writeFile("firstrun.sh", _firstrun);
_cmdline += " systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target";
if (!_userDefinedFirstRun) {
_firstrun = "#!/bin/bash\n\n" + QByteArray("set +e\n\n") + _firstrun;

// Add file cleanup and exit commands
_firstrun.append("\nrm -f /boot/firstrun.sh\n");
_firstrun.append("sed -i 's| systemd.run.*||g' /boot/cmdline.txt\n");
_firstrun.append("exit 0\n");
}

if (_initFormat == "systemd") {
fat->writeFile("firstrun.sh", _firstrun);
_cmdline += " systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target";
}
}

if (!_cloudinit.isEmpty() && _initFormat == "cloudinit")
Expand Down
8 changes: 7 additions & 1 deletion src/downloadthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ class DownloadThread : public QThread
*/
void setVerifyEnabled(bool verify);

/*
* Enable/disable USB Ethernet Gadget mode config
*/
void setEtherGadgetEnabled(bool etherGadget);

/*
* Enable disk cache
*/
Expand All @@ -116,7 +121,7 @@ class DownloadThread : public QThread
/*
* Enable image customization
*/
void setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun, const QByteArray &cloudinit, const QByteArray &cloudinitNetwork, const QByteArray &initFormat);
void setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun, const QByteArray &cloudinit, const QByteArray &cloudinitNetwork, const QByteArray &initFormat, const bool userDefinedFirstRun, const bool enableEtherGadget);

/*
* Thread safe download progress query functions
Expand Down Expand Up @@ -171,6 +176,7 @@ class DownloadThread : public QThread
std::uint64_t _lastFailureOffset;
qint64 _sectorsStart;
QByteArray _url, _useragent, _buf, _filename, _lastError, _expectedHash, _config, _cmdline, _firstrun, _cloudinit, _cloudinitNetwork, _initFormat;
bool _userDefinedFirstRun, _enableEtherGadget;
char *_firstBlock;
size_t _firstBlockSize;
static QByteArray _proxy;
Expand Down
8 changes: 8 additions & 0 deletions src/extraFiles.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/">
<file>extraFiles/10-usb.network</file>
<file>extraFiles/g_ether.conf</file>
<file>extraFiles/usb-ether-gadget.conf</file>
<file>extraFiles/rpi-usb-ether-gadget.sh</file>
paulober marked this conversation as resolved.
Show resolved Hide resolved
</qresource>
</RCC>
33 changes: 33 additions & 0 deletions src/extraFiles/10-usb.network
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[Match]
Name=usb*

[Link]
RequiredForOnline=no

[Network]
# Configure Subnet for USB Ethernet Gadget
# - IP Range: 10.12.194.1 to 10.12.194.14
# - Total IPs: 16
# - Usable IPs: 14
# - Network Address: 10.12.194.0
# - Broadcast Address: 10.12.194.15
# - Subnet Mask: 255.255.255.240 (/28)
# | 10 - private
# | 12 - 2012 founding of Raspberry Pi Ltd.
# | 194 - address of Raspberry Pi Ltd. in the Science Park, Cambridge
# | 1 - first device
# TODO: maybe only static hostname to not conflict when multiple devices are connected
# to the same computer
Address=10.12.194.1/28
DHCPServer=yes

[DHCPServer]
# Configure DHCP settings
PoolSize=16 # Number of IP addresses available for lease
DefaultLeaseTimeSec=60 # Default lease time for DHCP clients
MaxLeaseTimeSec=60 # Maximum lease time for DHCP clients

# Network isolation settings (no internet)
EmitDNS=no # Do not provide DNS information
EmitNTP=no # Do not provide NTP information
EmitRouter=no # Do not provide router information
1 change: 1 addition & 0 deletions src/extraFiles/g_ether.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
options g_ether idVendor=0x04b3 idProduct=0x4010 iManufacturer="Raspberry Pi" bcdDevice=0x0100 iProduct="USB Ethernet Gadget" iSerialNumber=<serial>
paulober marked this conversation as resolved.
Show resolved Hide resolved
50 changes: 50 additions & 0 deletions src/extraFiles/rpi-usb-ether-gadget.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash

# check if the script is run as root
if [ "$EUID" -ne 0 ]; then
echo -e "\e[31mPlease run this script as root\e[0m"
exit
fi

# check if the script is run on a Raspberry Pi
if ! grep -q "Raspberry Pi" /proc/device-tree/model; then
echo -e "\e[31mThis script is for Raspberry Pi only\e[0m"
exit
fi

FORCE_ON=false
FORCE_OFF=false
paulober marked this conversation as resolved.
Show resolved Hide resolved
# check if command line arguments to force on or to force off are provided if not continue with
# check else force
if [ "$1" == "on" ]; then
FORCE_ON=true
elif [ "$1" == "off" ]; then
FORCE_OFF=true
elif [ "$1" == "toggle" ]; then
: # Do nothing and continue to the code below
elif [ "$1" == "status" ]; then
if [ -f /etc/modules-load.d/usb-ether-gadget.conf ]; then
echo -e "\e[33mUSB Ethernet Gadget is on\e[0m"
else
echo -e "\e[33mUSB Ethernet Gadget is off\e[0m"
fi
exit
else # add else if to make toggling possible without toggle keyword
echo "Usage: rpi-usb-ether-gadget [on|off|toggle|status|help]"
exit
fi

#rm /etc/modprobe.d/g_ether.conf

# check if /etc/modules-load.d/usb-ether-gadget.conf exists to know if to turn of or on or the force flags are set
paulober marked this conversation as resolved.
Show resolved Hide resolved
if ([ -f /etc/modules-load.d/usb-ether-gadget.conf ] && [ "$FORCE_ON" = false ]) || [ "$FORCE_OFF" = true ]; then
paulober marked this conversation as resolved.
Show resolved Hide resolved
echo "Turning \e[31moff\e[0m USB Ethernet Gadget mode"
rm -f /etc/modules-load.d/usb-ether-gadget.conf
sed -i '/dtoverlay=dwc2,dr_mode=peripheral/d' /boot/firmware/config.txt
else
echo "Turning \e[32mon\e[0m USB Ethernet Gadget mode"
echo "dwc2\ng_ether\n" > /etc/modules-load.d/usb-ether-gadget.conf
echo "\ndtoverlay=dwc2,dr_mode=peripheral\n" >> /boot/firmware/config.txt
fi

echo "Reboot to apply changes"
2 changes: 2 additions & 0 deletions src/extraFiles/usb-ether-gadget.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dwc2
g_ether
Loading