Skip to content

Commit 7fd2aa9

Browse files
author
Codex
committed
Resolve shared HID slot for touchscreen gadget
The upstream wake HID function and the Android touchscreen digitizer both need hid.usb3 on this hardware. The device kernel does not expose hid.usb4, so keep wake_hid enabled only when touchscreen is disabled and route wake behavior through a harmless no-touch touchscreen report when touchscreen owns the slot. Also preserve enabled config symlinks when a disabled gadget item shares the same config path, which prevents disabled wake_hid from removing the active touchscreen hid.usb3 link during config writes. Validated with go test ./internal/usbgadget, make build_dev, and a live JetKVM sanity check showing the debug binary hash matched, USB was configured, and hid.usb3 used the touchscreen report length.
1 parent 2360679 commit 7fd2aa9

4 files changed

Lines changed: 134 additions & 2 deletions

File tree

internal/usbgadget/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ func (u *UsbGadget) isGadgetConfigItemEnabled(itemKey string) bool {
7777
return u.enabledDevices.RelativeMouse
7878
case "keyboard":
7979
return u.enabledDevices.Keyboard
80+
case "wake_hid":
81+
return !u.enabledDevices.Touchscreen
8082
case "touchscreen":
8183
return u.enabledDevices.Touchscreen
8284
case "mass_storage_base":

internal/usbgadget/config_test.go

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package usbgadget
22

3-
import "testing"
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/rs/zerolog"
8+
)
49

510
func TestAudioGadgetConfigFollowsEnabledDevice(t *testing.T) {
611
u := &UsbGadget{enabledDevices: Devices{Audio: false}}
@@ -16,9 +21,113 @@ func TestAudioGadgetConfigFollowsEnabledDevice(t *testing.T) {
1621

1722
func TestBaseGadgetConfigItemsAlwaysEnabled(t *testing.T) {
1823
u := &UsbGadget{}
19-
for _, item := range []string{"base", "base_info", "wake_hid"} {
24+
for _, item := range []string{"base", "base_info"} {
2025
if !u.isGadgetConfigItemEnabled(item) {
2126
t.Fatalf("%s should always be enabled", item)
2227
}
2328
}
2429
}
30+
31+
func TestWakeHIDUsesTouchscreenSlotOnlyWhenTouchscreenDisabled(t *testing.T) {
32+
u := &UsbGadget{enabledDevices: Devices{Touchscreen: false}}
33+
if !u.isGadgetConfigItemEnabled("wake_hid") {
34+
t.Fatal("wake_hid should be enabled when touchscreen is disabled")
35+
}
36+
37+
u.enabledDevices.Touchscreen = true
38+
if u.isGadgetConfigItemEnabled("wake_hid") {
39+
t.Fatal("wake_hid should be disabled when touchscreen uses the final HID slot")
40+
}
41+
}
42+
43+
func TestEnabledGadgetConfigPathsAreUnique(t *testing.T) {
44+
for name, devices := range map[string]Devices{
45+
"touchscreen": {
46+
AbsoluteMouse: true,
47+
RelativeMouse: true,
48+
Keyboard: true,
49+
Touchscreen: true,
50+
MassStorage: true,
51+
SerialConsole: true,
52+
Audio: true,
53+
},
54+
"wake_hid": {
55+
AbsoluteMouse: true,
56+
RelativeMouse: true,
57+
Keyboard: true,
58+
Touchscreen: false,
59+
MassStorage: true,
60+
SerialConsole: true,
61+
Audio: true,
62+
},
63+
} {
64+
t.Run(name, func(t *testing.T) {
65+
assertEnabledGadgetConfigPathsAreUnique(t, devices)
66+
})
67+
}
68+
}
69+
70+
func assertEnabledGadgetConfigPathsAreUnique(t *testing.T, devices Devices) {
71+
t.Helper()
72+
73+
u := &UsbGadget{enabledDevices: devices}
74+
75+
paths := map[string]string{}
76+
configPaths := map[string]string{}
77+
78+
for key, item := range defaultGadgetConfig {
79+
if !u.isGadgetConfigItemEnabled(key) {
80+
continue
81+
}
82+
83+
if len(item.path) > 0 {
84+
pathKey := strings.Join(item.path, "/")
85+
if previous, ok := paths[pathKey]; ok {
86+
t.Fatalf("%s and %s share gadget path %s", previous, key, pathKey)
87+
}
88+
paths[pathKey] = key
89+
}
90+
91+
if len(item.configPath) > 0 {
92+
configPathKey := strings.Join(item.configPath, "/")
93+
if previous, ok := configPaths[configPathKey]; ok {
94+
t.Fatalf("%s and %s share gadget config path %s", previous, key, configPathKey)
95+
}
96+
configPaths[configPathKey] = key
97+
}
98+
}
99+
}
100+
101+
func TestDisabledSharedConfigPathIsNotRemovedWhenEnabledItemUsesIt(t *testing.T) {
102+
logger := zerolog.Nop()
103+
tx := &UsbGadgetTransaction{
104+
c: &ChangeSet{},
105+
log: &logger,
106+
kvmGadgetPath: "/sys/kernel/config/usb_gadget/test",
107+
configC1Path: "/sys/kernel/config/usb_gadget/test/configs/c.1",
108+
orderedConfigItems: orderedGadgetConfigItems{{"wake_hid", wakeHIDConfig}, {"touchscreen", touchscreenConfig}},
109+
isGadgetConfigItemEnabled: func(key string) bool {
110+
return key == "touchscreen"
111+
},
112+
}
113+
114+
tx.WriteGadgetConfig()
115+
116+
touchscreenConfigPath := "/sys/kernel/config/usb_gadget/test/configs/c.1/hid.usb3"
117+
for _, change := range tx.c.Changes {
118+
if change.Path == touchscreenConfigPath && change.ExpectedState == FileStateAbsent && change.When == "" {
119+
t.Fatalf("disabled wake_hid should not request unconditional removal of enabled touchscreen config path: %s", change.String())
120+
}
121+
}
122+
123+
if tx.reorderSymlinkChanges == nil {
124+
t.Fatal("expected touchscreen config path to be included in symlink reorder")
125+
}
126+
127+
for _, link := range tx.reorderSymlinkChanges.ParamSymlinks {
128+
if link.Path == touchscreenConfigPath {
129+
return
130+
}
131+
}
132+
t.Fatalf("expected touchscreen config path %s in symlink reorder", touchscreenConfigPath)
133+
}

internal/usbgadget/config_tx.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,29 @@ func (tx *UsbGadgetTransaction) WriteGadgetConfig() {
152152
deps := make([]string, 0)
153153
deps = append(deps, tx.kvmGadgetPath)
154154

155+
enabledConfigPaths := map[string]struct{}{}
156+
for _, val := range tx.orderedConfigItems {
157+
if !tx.isGadgetConfigItemEnabled(val.key) {
158+
continue
159+
}
160+
if val.item.configPath == nil || val.item.configAttrs != nil {
161+
continue
162+
}
163+
enabledConfigPaths[joinPath(tx.configC1Path, val.item.configPath)] = struct{}{}
164+
}
165+
155166
for _, val := range tx.orderedConfigItems {
156167
key := val.key
157168
item := val.item
158169

159170
// check if the item is enabled in the config
160171
if !tx.isGadgetConfigItemEnabled(key) {
172+
if item.configPath != nil && item.configAttrs == nil {
173+
configPath := joinPath(tx.configC1Path, item.configPath)
174+
if _, sharedWithEnabledItem := enabledConfigPaths[configPath]; sharedWithEnabledItem {
175+
continue
176+
}
177+
}
161178
tx.DisableGadgetItemConfig(item)
162179
continue
163180
}

internal/usbgadget/hid_keyboard.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,10 @@ func (u *UsbGadget) KeyboardReport(modifier byte, keys []byte) error {
529529
}
530530

531531
func (u *UsbGadget) WakeReport(active bool) error {
532+
if u.enabledDevices.Touchscreen {
533+
return u.TouchscreenReport(0, 0, false)
534+
}
535+
532536
var report byte
533537
if active {
534538
report = 1

0 commit comments

Comments
 (0)