Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
51 changes: 40 additions & 11 deletions internal/usbgadget/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ type gadgetConfigItemWithKey struct {

type orderedGadgetConfigItems []gadgetConfigItemWithKey

type UsbResetMode uint8

const (
UsbResetNever UsbResetMode = 0 // never reset the usb gadget
UsbResetAlways UsbResetMode = 1 // always reset the usb gadget
UsbResetOnDemand UsbResetMode = 2 // reset the usb gadget when needed
)

var defaultGadgetConfig = map[string]gadgetConfigItem{
"base": {
order: 0,
Expand Down Expand Up @@ -177,36 +185,57 @@ func (u *UsbGadget) Init() error {

u.udc = udcs[0]

err := u.configureUsbGadget(false)
err := u.configureUsbGadget(UsbResetAlways)
if err != nil {
return u.logError("unable to initialize USB stack", err)
}

return nil
}

func (u *UsbGadget) UpdateGadgetConfig() error {
func (u *UsbGadget) UpdateGadgetConfig(resetUsbMode UsbResetMode) error {
u.configLock.Lock()
defer u.configLock.Unlock()

u.loadGadgetConfig()

err := u.configureUsbGadget(true)
err := u.configureUsbGadget(resetUsbMode)
if err != nil {
return u.logError("unable to update gadget config", err)
}

return nil
}

func (u *UsbGadget) configureUsbGadget(resetUsb bool) error {
return u.WithTransaction(func() error {
u.tx.MountConfigFS()
u.tx.CreateConfigPath()
u.tx.WriteGadgetConfig()
if resetUsb {
u.tx.RebindUsb(true)
func (u *UsbGadget) configureUsbGadget(resetUsbMode UsbResetMode) error {
f := func(resetUsbAfter bool) func() error {
return func() error {
u.tx.MountConfigFS()
u.tx.CreateConfigPath()
u.tx.WriteGadgetConfig()
if resetUsbAfter {
u.tx.RebindUsb(true)
}
return nil
}
}

// initial attempt to configure the gadget
err := u.WithTransaction(f(resetUsbMode == UsbResetAlways))
if err == nil {
return nil
})
}

// if the initial attempt failed, try to configure the gadget again with the resetUsb flag
if resetUsbMode == UsbResetOnDemand {
u.log.Warn().Err(err).Msg("initial attempt to configure the gadget failed, resetting USB gadget then trying again")
// NOTES: do not use the RebindUsb method here because it will block the transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...do not use the public RebindUsb method...

I completely misread this the first time ;)

if err := u.rebindUsb(true); err != nil {
u.log.Warn().Err(err).Msg("failed to reset USB gadget, skipping second attempt")
return err
}
return u.WithTransaction(f(true))
}

return err
}
10 changes: 5 additions & 5 deletions jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ func rpcSetUsbConfig(usbConfig usbgadget.Config) error {
LoadConfig()
config.UsbConfig = &usbConfig
gadget.SetGadgetConfig(config.UsbConfig)
return updateUsbRelatedConfig()
return updateUsbRelatedConfig(usbgadget.UsbResetAlways)
}

func rpcGetWakeOnLanDevices() ([]WakeOnLanDevice, error) {
Expand Down Expand Up @@ -890,8 +890,8 @@ func rpcGetUsbDevices() (usbgadget.Devices, error) {
return *config.UsbDevices, nil
}

func updateUsbRelatedConfig() error {
if err := gadget.UpdateGadgetConfig(); err != nil {
func updateUsbRelatedConfig(resetUsbMode usbgadget.UsbResetMode) error {
if err := gadget.UpdateGadgetConfig(resetUsbMode); err != nil {
return fmt.Errorf("failed to write gadget config: %w", err)
}
if err := SaveConfig(); err != nil {
Expand All @@ -903,7 +903,7 @@ func updateUsbRelatedConfig() error {
func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
config.UsbDevices = &usbDevices
gadget.SetGadgetDevices(config.UsbDevices)
return updateUsbRelatedConfig()
return updateUsbRelatedConfig(usbgadget.UsbResetOnDemand)
}

func rpcSetUsbDeviceState(device string, enabled bool) error {
Expand All @@ -920,7 +920,7 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
return fmt.Errorf("invalid device: %s", device)
}
gadget.SetGadgetDevices(config.UsbDevices)
return updateUsbRelatedConfig()
return updateUsbRelatedConfig(usbgadget.UsbResetAlways)
}

func rpcSetCloudUrl(apiUrl string, appUrl string) error {
Expand Down
3 changes: 2 additions & 1 deletion ui/src/routes/devices.$id.settings.advanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TextAreaWithLabel } from "@components/TextArea";
import { isOnDevice } from "@/main";
import notifications from "@/notifications";
import { m } from "@localizations/messages.js";
import { sleep } from "@/utils";

export default function SettingsAdvancedRoute() {
const { send } = useJsonRpc();
Expand Down Expand Up @@ -311,7 +312,7 @@ export default function SettingsAdvancedRoute() {
size="SM"
theme="light"
text={m.advanced_reset_config_button()}
onClick={() => {
onClick={async () => {
handleResetConfig();
// Add 2s delay between resetting the configuration and calling reload() to prevent reload from interrupting the RPC call to reset things.
await sleep(2000);
Expand Down
3 changes: 2 additions & 1 deletion ui/src/routes/devices.$id.settings.general.reboot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { useNavigate } from "react-router";
import { useJsonRpc } from "@hooks/useJsonRpc";
import { Button } from "@components/Button";
import { m } from "@localizations/messages.js";
import { sleep } from "@/utils";

export default function SettingsGeneralRebootRoute() {
const navigate = useNavigate();
const { send } = useJsonRpc();

const onClose = useCallback(() => {
const onClose = useCallback(async () => {
navigate(".."); // back to the devices.$id.settings page
// Add 1s delay between navigation and calling reload() to prevent reload from interrupting the navigation.
await sleep(1000);
Expand Down
2 changes: 1 addition & 1 deletion ui/src/routes/devices.$id.settings.general.update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function SettingsGeneralUpdateRoute() {
const { setModalView, otaState } = useUpdateStore();
const { send } = useJsonRpc();

const onClose = useCallback(() => {
const onClose = useCallback(async () => {
navigate(".."); // back to the devices.$id.settings page
// Add 1s delay between navigation and calling reload() to prevent reload from interrupting the navigation.
await sleep(1000);
Expand Down
16 changes: 7 additions & 9 deletions usb_mass_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ import (
"github.com/pion/webrtc/v4"
"github.com/psanford/httpreadat"

"github.com/jetkvm/kvm/internal/usbgadget"
"github.com/jetkvm/kvm/resource"
)

func writeFile(path string, data string) error {
return os.WriteFile(path, []byte(data), 0644)
}

func getMassStorageImage() (string, error) {
massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
if err != nil {
Expand All @@ -40,14 +37,15 @@ func getMassStorageImage() (string, error) {
}

func setMassStorageImage(imagePath string) error {
massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
err, _ := gadget.OverrideGadgetConfig("mass_storage_lun0", "file", imagePath)
if err != nil {
return fmt.Errorf("failed to get mass storage path: %w", err)
return fmt.Errorf("failed to set mass storage path: %w", err)
}

if err := writeFile(path.Join(massStorageFunctionPath, "file"), imagePath); err != nil {
return fmt.Errorf("failed to set image path: %w", err)
if err := gadget.UpdateGadgetConfig(usbgadget.UsbResetOnDemand); err != nil {
return fmt.Errorf("failed to update gadget config: %w", err)
}

return nil
}

Expand All @@ -66,7 +64,7 @@ func setMassStorageMode(cdrom bool) error {
return nil
}

return gadget.UpdateGadgetConfig()
return gadget.UpdateGadgetConfig(usbgadget.UsbResetOnDemand)
}

func mountImage(imagePath string) error {
Expand Down
Loading