Skip to content

Commit 63c2272

Browse files
authored
feat(usb_mass_storage): mount as disk (#333)
* feat(usb_mass_storage): mount as disk * chore: try to set initial virtual media state from sysfs * chore(usb-mass-storage): fix inquiry_string
1 parent 8ee0532 commit 63c2272

File tree

6 files changed

+137
-27
lines changed

6 files changed

+137
-27
lines changed

internal/usbgadget/config.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,29 @@ func (u *UsbGadget) GetPath(itemKey string) (string, error) {
137137
return joinPath(u.kvmGadgetPath, item.path), nil
138138
}
139139

140+
// OverrideGadgetConfig overrides the gadget config for the given item and attribute.
141+
// It returns an error if the item is not found or the attribute is not found.
142+
// It returns true if the attribute is overridden, false otherwise.
143+
func (u *UsbGadget) OverrideGadgetConfig(itemKey string, itemAttr string, value string) (error, bool) {
144+
u.configLock.Lock()
145+
defer u.configLock.Unlock()
146+
147+
// get it as a pointer
148+
_, ok := u.configMap[itemKey]
149+
if !ok {
150+
return fmt.Errorf("config item %s not found", itemKey), false
151+
}
152+
153+
if u.configMap[itemKey].attrs[itemAttr] == value {
154+
return nil, false
155+
}
156+
157+
u.configMap[itemKey].attrs[itemAttr] = value
158+
u.log.Info().Str("itemKey", itemKey).Str("itemAttr", itemAttr).Str("value", value).Msg("overriding gadget config")
159+
160+
return nil, true
161+
}
162+
140163
func mountConfigFS() error {
141164
_, err := os.Stat(gadgetPath)
142165
// TODO: check if it's mounted properly

internal/usbgadget/mass_storage.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ var massStorageLun0Config = gadgetConfigItem{
1414
order: 3001,
1515
path: []string{"functions", "mass_storage.usb0", "lun.0"},
1616
attrs: gadgetAttributes{
17-
"cdrom": "1",
18-
"ro": "1",
19-
"removable": "1",
20-
"file": "\n",
21-
"inquiry_string": "JetKVM Virtual Media",
17+
"cdrom": "1",
18+
"ro": "1",
19+
"removable": "1",
20+
"file": "\n",
21+
// the additional whitespace is intentional to avoid the "JetKVM V irtual Media" string
22+
// https://github.com/jetkvm/rv1106-system/blob/778133a1c153041e73f7de86c9c434a2753ea65d/sysdrv/source/uboot/u-boot/drivers/usb/gadget/f_mass_storage.c#L2556
23+
// Vendor (8 chars), product (16 chars)
24+
"inquiry_string": "JetKVM Virtual Media",
2225
},
2326
}

jsonrpc.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,12 @@ type RPCHandler struct {
566566
func rpcSetMassStorageMode(mode string) (string, error) {
567567
logger.Info().Str("mode", mode).Msg("Setting mass storage mode")
568568
var cdrom bool
569-
if mode == "cdrom" {
569+
switch mode {
570+
case "cdrom":
570571
cdrom = true
571-
} else if mode != "file" {
572+
case "file":
573+
cdrom = false
574+
default:
572575
logger.Info().Str("mode", mode).Msg("Invalid mode provided")
573576
return "", fmt.Errorf("invalid mode: %s", mode)
574577
}
@@ -587,7 +590,7 @@ func rpcSetMassStorageMode(mode string) (string, error) {
587590
}
588591

589592
func rpcGetMassStorageMode() (string, error) {
590-
cdrom, err := getMassStorageMode()
593+
cdrom, err := getMassStorageCDROMEnabled()
591594
if err != nil {
592595
return "", fmt.Errorf("failed to get mass storage mode: %w", err)
593596
}

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ func Main() {
7777

7878
initUsbGadget()
7979

80+
err = setInitialVirtualMediaState()
81+
if err != nil {
82+
logger.Warn().Err(err).Msg("failed to set initial virtual media state")
83+
}
84+
8085
go func() {
8186
time.Sleep(15 * time.Minute)
8287
for {

ui/src/routes/devices.$id.mount.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ function BrowserFileView({
414414
if (file?.name.endsWith(".iso")) {
415415
setUsbMode("CDROM");
416416
} else if (file?.name.endsWith(".img")) {
417-
setUsbMode("CDROM");
417+
setUsbMode("Disk");
418418
}
419419
};
420420

@@ -566,7 +566,7 @@ function UrlView({
566566
if (url.endsWith(".iso")) {
567567
setUsbMode("CDROM");
568568
} else if (url.endsWith(".img")) {
569-
setUsbMode("CDROM");
569+
setUsbMode("Disk");
570570
}
571571
}
572572

@@ -773,7 +773,7 @@ function DeviceFileView({
773773
if (file.name.endsWith(".iso")) {
774774
setUsbMode("CDROM");
775775
} else if (file.name.endsWith(".img")) {
776-
setUsbMode("CDROM");
776+
setUsbMode("Disk");
777777
}
778778
}
779779

@@ -1579,7 +1579,6 @@ function UsbModeSelector({
15791579
type="radio"
15801580
id="disk"
15811581
name="mountType"
1582-
disabled
15831582
checked={usbMode === "Disk"}
15841583
onChange={() => setUsbMode("Disk")}
15851584
className="h-3 w-3 border-slate-800/30 bg-white text-blue-700 transition-opacity focus:ring-blue-500 disabled:opacity-30 dark:bg-slate-800"
@@ -1588,9 +1587,6 @@ function UsbModeSelector({
15881587
<span className="text-sm font-medium leading-none text-slate-900 opacity-50 dark:text-white">
15891588
Disk
15901589
</span>
1591-
<div className="text-[10px] text-slate-500 dark:text-slate-400">
1592-
Coming soon
1593-
</div>
15941590
</div>
15951591
</label>
15961592
</div>

usb_mass_storage.go

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ func writeFile(path string, data string) error {
2626
return os.WriteFile(path, []byte(data), 0644)
2727
}
2828

29+
func getMassStorageImage() (string, error) {
30+
massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
31+
if err != nil {
32+
return "", fmt.Errorf("failed to get mass storage path: %w", err)
33+
}
34+
35+
imagePath, err := os.ReadFile(path.Join(massStorageFunctionPath, "file"))
36+
if err != nil {
37+
return "", fmt.Errorf("failed to get mass storage image path: %w", err)
38+
}
39+
return strings.TrimSpace(string(imagePath)), nil
40+
}
41+
2942
func setMassStorageImage(imagePath string) error {
3043
massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
3144
if err != nil {
@@ -39,19 +52,21 @@ func setMassStorageImage(imagePath string) error {
3952
}
4053

4154
func setMassStorageMode(cdrom bool) error {
42-
massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
43-
if err != nil {
44-
return fmt.Errorf("failed to get mass storage path: %w", err)
45-
}
46-
4755
mode := "0"
4856
if cdrom {
4957
mode = "1"
5058
}
51-
if err := writeFile(path.Join(massStorageFunctionPath, "lun.0", "cdrom"), mode); err != nil {
59+
60+
err, changed := gadget.OverrideGadgetConfig("mass_storage_lun0", "cdrom", mode)
61+
if err != nil {
5262
return fmt.Errorf("failed to set cdrom mode: %w", err)
5363
}
54-
return nil
64+
65+
if !changed {
66+
return nil
67+
}
68+
69+
return gadget.UpdateGadgetConfig()
5570
}
5671

5772
func onDiskMessage(msg webrtc.DataChannelMessage) {
@@ -113,20 +128,17 @@ func rpcMountBuiltInImage(filename string) error {
113128
return mountImage(imagePath)
114129
}
115130

116-
func getMassStorageMode() (bool, error) {
131+
func getMassStorageCDROMEnabled() (bool, error) {
117132
massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
118133
if err != nil {
119134
return false, fmt.Errorf("failed to get mass storage path: %w", err)
120135
}
121-
122-
data, err := os.ReadFile(path.Join(massStorageFunctionPath, "lun.0", "cdrom"))
136+
data, err := os.ReadFile(path.Join(massStorageFunctionPath, "cdrom"))
123137
if err != nil {
124138
return false, fmt.Errorf("failed to read cdrom mode: %w", err)
125139
}
126-
127140
// Trim any whitespace characters. It has a newline at the end
128141
trimmedData := strings.TrimSpace(string(data))
129-
130142
return trimmedData == "1", nil
131143
}
132144

@@ -191,6 +203,60 @@ func rpcUnmountImage() error {
191203

192204
var httpRangeReader *httpreadat.RangeReader
193205

206+
func getInitialVirtualMediaState() (*VirtualMediaState, error) {
207+
cdromEnabled, err := getMassStorageCDROMEnabled()
208+
if err != nil {
209+
return nil, fmt.Errorf("failed to get mass storage cdrom enabled: %w", err)
210+
}
211+
212+
diskPath, err := getMassStorageImage()
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to get mass storage image: %w", err)
215+
}
216+
217+
initialState := &VirtualMediaState{
218+
Source: Storage,
219+
Mode: Disk,
220+
}
221+
222+
if cdromEnabled {
223+
initialState.Mode = CDROM
224+
}
225+
226+
// TODO: check if it's WebRTC or HTTP
227+
if diskPath == "" {
228+
return nil, nil
229+
} else if diskPath == "/dev/nbd0" {
230+
initialState.Source = HTTP
231+
initialState.URL = "/"
232+
initialState.Size = 1
233+
} else {
234+
initialState.Filename = filepath.Base(diskPath)
235+
// get size from file
236+
logger.Info().Str("diskPath", diskPath).Msg("getting file size")
237+
info, err := os.Stat(diskPath)
238+
if err != nil {
239+
return nil, fmt.Errorf("failed to get file info: %w", err)
240+
}
241+
initialState.Size = info.Size()
242+
}
243+
244+
return initialState, nil
245+
}
246+
247+
func setInitialVirtualMediaState() error {
248+
virtualMediaStateMutex.Lock()
249+
defer virtualMediaStateMutex.Unlock()
250+
initialState, err := getInitialVirtualMediaState()
251+
if err != nil {
252+
return fmt.Errorf("failed to get initial virtual media state: %w", err)
253+
}
254+
currentVirtualMediaState = initialState
255+
256+
logger.Info().Interface("initial_virtual_media_state", initialState).Msg("initial virtual media state set")
257+
return nil
258+
}
259+
194260
func rpcMountWithHTTP(url string, mode VirtualMediaMode) error {
195261
virtualMediaStateMutex.Lock()
196262
if currentVirtualMediaState != nil {
@@ -204,6 +270,11 @@ func rpcMountWithHTTP(url string, mode VirtualMediaMode) error {
204270
return fmt.Errorf("failed to use http url: %w", err)
205271
}
206272
logger.Info().Str("url", url).Int64("size", n).Msg("using remote url")
273+
274+
if err := setMassStorageMode(mode == CDROM); err != nil {
275+
return fmt.Errorf("failed to set mass storage mode: %w", err)
276+
}
277+
207278
currentVirtualMediaState = &VirtualMediaState{
208279
Source: HTTP,
209280
Mode: mode,
@@ -243,6 +314,11 @@ func rpcMountWithWebRTC(filename string, size int64, mode VirtualMediaMode) erro
243314
Size: size,
244315
}
245316
virtualMediaStateMutex.Unlock()
317+
318+
if err := setMassStorageMode(mode == CDROM); err != nil {
319+
return fmt.Errorf("failed to set mass storage mode: %w", err)
320+
}
321+
246322
logger.Debug().Interface("currentVirtualMediaState", currentVirtualMediaState).Msg("currentVirtualMediaState")
247323
logger.Debug().Msg("Starting nbd device")
248324
nbdDevice = NewNBDDevice()
@@ -280,6 +356,10 @@ func rpcMountWithStorage(filename string, mode VirtualMediaMode) error {
280356
return fmt.Errorf("failed to get file info: %w", err)
281357
}
282358

359+
if err := setMassStorageMode(mode == CDROM); err != nil {
360+
return fmt.Errorf("failed to set mass storage mode: %w", err)
361+
}
362+
283363
err = setMassStorageImage(fullPath)
284364
if err != nil {
285365
return fmt.Errorf("failed to set mass storage image: %w", err)

0 commit comments

Comments
 (0)