mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-20 02:04:15 +01:00
chore(usbgadget): update usbgadget config only when needed (#474)
This commit is contained in:
329
internal/usbgadget/config_tx.go
Normal file
329
internal/usbgadget/config_tx.go
Normal file
@@ -0,0 +1,329 @@
|
||||
package usbgadget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// no os package should occur in this file
|
||||
|
||||
type UsbGadgetTransaction struct {
|
||||
c *ChangeSet
|
||||
|
||||
// below are the fields that are needed to be set by the caller
|
||||
log *zerolog.Logger
|
||||
udc string
|
||||
dwc3Path string
|
||||
kvmGadgetPath string
|
||||
configC1Path string
|
||||
orderedConfigItems orderedGadgetConfigItems
|
||||
isGadgetConfigItemEnabled func(key string) bool
|
||||
|
||||
reorderSymlinkChanges *RequestedFileChange
|
||||
}
|
||||
|
||||
func (u *UsbGadget) newUsbGadgetTransaction(lock bool) error {
|
||||
if lock {
|
||||
u.txLock.Lock()
|
||||
defer u.txLock.Unlock()
|
||||
}
|
||||
|
||||
if u.tx != nil {
|
||||
return fmt.Errorf("transaction already exists")
|
||||
}
|
||||
|
||||
tx := &UsbGadgetTransaction{
|
||||
c: &ChangeSet{},
|
||||
log: u.log,
|
||||
udc: u.udc,
|
||||
dwc3Path: dwc3Path,
|
||||
kvmGadgetPath: u.kvmGadgetPath,
|
||||
configC1Path: u.configC1Path,
|
||||
orderedConfigItems: u.getOrderedConfigItems(),
|
||||
isGadgetConfigItemEnabled: u.isGadgetConfigItemEnabled,
|
||||
}
|
||||
u.tx = tx
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UsbGadget) WithTransaction(fn func() error) error {
|
||||
u.txLock.Lock()
|
||||
defer u.txLock.Unlock()
|
||||
|
||||
err := u.newUsbGadgetTransaction(false)
|
||||
if err != nil {
|
||||
u.log.Error().Err(err).Msg("failed to create transaction")
|
||||
return err
|
||||
}
|
||||
if err := fn(); err != nil {
|
||||
u.log.Error().Err(err).Msg("transaction failed")
|
||||
return err
|
||||
}
|
||||
result := u.tx.Commit()
|
||||
u.tx = nil
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) addFileChange(component string, change RequestedFileChange) string {
|
||||
change.Component = component
|
||||
tx.c.AddFileChangeStruct(change)
|
||||
|
||||
key := change.Key
|
||||
if key == "" {
|
||||
key = change.Path
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) mkdirAll(component string, path string, description string) string {
|
||||
return tx.addFileChange(component, RequestedFileChange{
|
||||
Path: path,
|
||||
ExpectedState: FileStateDirectory,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) removeFile(component string, path string, description string) string {
|
||||
return tx.addFileChange(component, RequestedFileChange{
|
||||
Path: path,
|
||||
ExpectedState: FileStateAbsent,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) Commit() error {
|
||||
tx.addFileChange("gadget-finalize", *tx.reorderSymlinkChanges)
|
||||
|
||||
err := tx.c.Apply()
|
||||
if err != nil {
|
||||
tx.log.Error().Err(err).Msg("failed to update usbgadget configuration")
|
||||
return err
|
||||
}
|
||||
tx.log.Info().Msg("usbgadget configuration updated")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UsbGadget) getOrderedConfigItems() orderedGadgetConfigItems {
|
||||
items := make([]gadgetConfigItemWithKey, 0)
|
||||
for key, item := range u.configMap {
|
||||
items = append(items, gadgetConfigItemWithKey{key, item})
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].item.order < items[j].item.order
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) MountConfigFS() {
|
||||
tx.addFileChange("gadget", RequestedFileChange{
|
||||
Path: configFSPath,
|
||||
ExpectedState: FileStateMountedConfigFS,
|
||||
Description: "mount configfs",
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) CreateConfigPath() {
|
||||
tx.mkdirAll("gadget", tx.configC1Path, "create config path")
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) WriteGadgetConfig() {
|
||||
// create kvm gadget path
|
||||
tx.mkdirAll("gadget", tx.kvmGadgetPath, "create kvm gadget path")
|
||||
|
||||
deps := make([]string, 0)
|
||||
|
||||
for _, val := range tx.orderedConfigItems {
|
||||
key := val.key
|
||||
item := val.item
|
||||
|
||||
// check if the item is enabled in the config
|
||||
if !tx.isGadgetConfigItemEnabled(key) {
|
||||
tx.DisableGadgetItemConfig(item)
|
||||
continue
|
||||
}
|
||||
deps = tx.writeGadgetItemConfig(item, deps)
|
||||
}
|
||||
|
||||
tx.WriteUDC()
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) getDisableKeys() []string {
|
||||
disableKeys := make([]string, 0)
|
||||
for _, item := range tx.orderedConfigItems {
|
||||
if !tx.isGadgetConfigItemEnabled(item.key) {
|
||||
continue
|
||||
}
|
||||
if item.item.configPath == nil || item.item.configAttrs != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
disableKeys = append(disableKeys, fmt.Sprintf("disable-%s", item.item.device))
|
||||
}
|
||||
return disableKeys
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) DisableGadgetItemConfig(item gadgetConfigItem) {
|
||||
// remove symlink if exists
|
||||
if item.configPath == nil {
|
||||
return
|
||||
}
|
||||
|
||||
configPath := joinPath(tx.configC1Path, item.configPath)
|
||||
_ = tx.removeFile("gadget", configPath, "remove symlink: disable gadget config")
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) writeGadgetItemConfig(item gadgetConfigItem, deps []string) []string {
|
||||
component := item.device
|
||||
|
||||
// create directory for the item
|
||||
files := make([]string, 0)
|
||||
files = append(files, deps...)
|
||||
|
||||
gadgetItemPath := joinPath(tx.kvmGadgetPath, item.path)
|
||||
files = append(files, tx.mkdirAll(component, gadgetItemPath, "create gadget item directory"))
|
||||
|
||||
beforeChange := make([]string, 0)
|
||||
disableGadgetItemKey := fmt.Sprintf("disable-%s", item.device)
|
||||
if item.configPath != nil && item.configAttrs == nil {
|
||||
beforeChange = append(beforeChange, tx.getDisableKeys()...)
|
||||
}
|
||||
|
||||
if len(item.attrs) > 0 {
|
||||
// write attributes for the item
|
||||
files = append(files, tx.writeGadgetAttrs(
|
||||
gadgetItemPath,
|
||||
item.attrs,
|
||||
component,
|
||||
beforeChange,
|
||||
)...)
|
||||
}
|
||||
|
||||
// write report descriptor if available
|
||||
reportDescPath := path.Join(gadgetItemPath, "report_desc")
|
||||
if item.reportDesc != nil {
|
||||
tx.addFileChange(component, RequestedFileChange{
|
||||
Path: reportDescPath,
|
||||
ExpectedState: FileStateFileContentMatch,
|
||||
ExpectedContent: item.reportDesc,
|
||||
Description: "write report descriptor",
|
||||
BeforeChange: beforeChange,
|
||||
DependsOn: files,
|
||||
})
|
||||
} else {
|
||||
tx.addFileChange(component, RequestedFileChange{
|
||||
Path: reportDescPath,
|
||||
ExpectedState: FileStateAbsent,
|
||||
Description: "remove report descriptor",
|
||||
BeforeChange: beforeChange,
|
||||
DependsOn: files,
|
||||
})
|
||||
}
|
||||
files = append(files, reportDescPath)
|
||||
|
||||
// create config directory if configAttrs are set
|
||||
if len(item.configAttrs) > 0 {
|
||||
configItemPath := joinPath(tx.configC1Path, item.configPath)
|
||||
tx.mkdirAll(component, configItemPath, "create config item directory")
|
||||
files = append(files, tx.writeGadgetAttrs(
|
||||
configItemPath,
|
||||
item.configAttrs,
|
||||
component,
|
||||
beforeChange,
|
||||
)...)
|
||||
}
|
||||
|
||||
// create symlink if configPath is set
|
||||
if item.configPath != nil && item.configAttrs == nil {
|
||||
configPath := joinPath(tx.configC1Path, item.configPath)
|
||||
|
||||
// the change will be only applied by `beforeChange`
|
||||
tx.addFileChange(component, RequestedFileChange{
|
||||
Key: disableGadgetItemKey,
|
||||
Path: configPath,
|
||||
ExpectedState: FileStateAbsent,
|
||||
When: "beforeChange", // TODO: make it more flexible
|
||||
Description: "remove symlink",
|
||||
})
|
||||
|
||||
tx.addReorderSymlinkChange(configPath, gadgetItemPath, files)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) writeGadgetAttrs(basePath string, attrs gadgetAttributes, component string, beforeChange []string) (files []string) {
|
||||
files = make([]string, 0)
|
||||
for key, val := range attrs {
|
||||
filePath := filepath.Join(basePath, key)
|
||||
tx.addFileChange(component, RequestedFileChange{
|
||||
Path: filePath,
|
||||
ExpectedState: FileStateFileContentMatch,
|
||||
ExpectedContent: []byte(val),
|
||||
Description: "write gadget attribute",
|
||||
DependsOn: []string{basePath},
|
||||
BeforeChange: beforeChange,
|
||||
})
|
||||
files = append(files, filePath)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) addReorderSymlinkChange(path string, target string, deps []string) {
|
||||
tx.log.Trace().Str("path", path).Str("target", target).Msg("add reorder symlink change")
|
||||
|
||||
if tx.reorderSymlinkChanges == nil {
|
||||
tx.reorderSymlinkChanges = &RequestedFileChange{
|
||||
Component: "gadget-finalize",
|
||||
Key: "reorder-symlinks",
|
||||
Path: tx.configC1Path,
|
||||
ExpectedState: FileStateSymlinkInOrderConfigFS,
|
||||
Description: "order symlinks",
|
||||
ParamSymlinks: []symlink{},
|
||||
}
|
||||
}
|
||||
|
||||
tx.reorderSymlinkChanges.DependsOn = append(tx.reorderSymlinkChanges.DependsOn, deps...)
|
||||
tx.reorderSymlinkChanges.ParamSymlinks = append(tx.reorderSymlinkChanges.ParamSymlinks, symlink{
|
||||
Path: path,
|
||||
Target: target,
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) WriteUDC() {
|
||||
// bound the gadget to a UDC (USB Device Controller)
|
||||
path := path.Join(tx.kvmGadgetPath, "UDC")
|
||||
tx.addFileChange("udc", RequestedFileChange{
|
||||
Path: path,
|
||||
ExpectedState: FileStateFileContentMatch,
|
||||
ExpectedContent: []byte(tx.udc),
|
||||
DependsOn: []string{"reorder-symlinks"},
|
||||
Description: "write UDC",
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *UsbGadgetTransaction) RebindUsb(ignoreUnbindError bool) {
|
||||
// remove the gadget from the UDC
|
||||
tx.addFileChange("udc", RequestedFileChange{
|
||||
Path: path.Join(tx.dwc3Path, "unbind"),
|
||||
ExpectedState: FileStateFileWrite,
|
||||
ExpectedContent: []byte(tx.udc),
|
||||
Description: "unbind UDC",
|
||||
})
|
||||
// bind the gadget to the UDC
|
||||
tx.addFileChange("udc", RequestedFileChange{
|
||||
Path: path.Join(tx.dwc3Path, "bind"),
|
||||
ExpectedState: FileStateFileWrite,
|
||||
ExpectedContent: []byte(tx.udc),
|
||||
Description: "bind UDC",
|
||||
DependsOn: []string{path.Join(tx.dwc3Path, "unbind")},
|
||||
IgnoreErrors: ignoreUnbindError,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user