Files
cloudpods/pkg/hostman/isolated_device/usb.go

587 lines
14 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package isolated_device
import (
"fmt"
"sort"
"strconv"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/hostman/guestman/desc"
"yunion.io/x/onecloud/pkg/util/regutils2"
)
type sUSBDevice struct {
*SBaseDevice
lsusbLine *sLsusbLine
PortPath string
}
// TODO: rename PCIDevice
func newUSBDevice(dev *PCIDevice, lsusbLine *sLsusbLine) *sUSBDevice {
return &sUSBDevice{
SBaseDevice: NewBaseDevice(dev, api.USB_TYPE, api.DEVICE_SHARING_MODE_EXCLUSIVE),
lsusbLine: lsusbLine,
}
}
func (dev *sUSBDevice) SetPortPath(portPath string) {
dev.PortPath = portPath
if len(dev.PortPath) > 0 {
dev.dev.Addr = fmt.Sprintf("%s:%s", dev.dev.Addr, portPath)
}
}
func (dev *sUSBDevice) GetCPUCmd() string {
return ""
}
func (dev *sUSBDevice) GetVGACmd() string {
return ""
}
func (dev *sUSBDevice) CustomProbe(int) error {
// do nothing
return nil
}
func GetUSBDevId(vendorId, devId, bus, addr string) string {
return fmt.Sprintf("dev_%s_%s-%s_%s", vendorId, devId, bus, addr)
}
func getUSBDevQemuOptions(vendorId, deviceId string, bus, addr, port string) (map[string]string, error) {
// id := GetUSBDevId(vendorId, deviceId, bus, addr)
busI, err := strconv.Atoi(bus)
if err != nil {
return nil, errors.Wrapf(err, "parse bus to int %q", bus)
}
addrI, err := strconv.Atoi(addr)
if err != nil {
return nil, errors.Wrapf(err, "parse addr to int %q", bus)
}
if len(port) > 0 {
return map[string]string{
"hostbus": fmt.Sprintf("%d", busI),
"hostport": port,
}, nil
}
return map[string]string{
// "id": id,
// "bus": "usb.0",
"vendorid": fmt.Sprintf("0x%s", vendorId),
"productid": fmt.Sprintf("0x%s", deviceId),
"hostbus": fmt.Sprintf("%d", busI),
"hostaddr": fmt.Sprintf("%d", addrI),
}, nil
}
func GetUSBDevQemuOptions(vendorDevId string, addr string) (map[string]string, error) {
parts := strings.Split(vendorDevId, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid vendor_device_id %q", vendorDevId)
}
vendorId := parts[0]
productId := parts[1]
addrParts := strings.Split(addr, ":")
if len(addrParts) != 2 && len(addrParts) != 3 {
return nil, fmt.Errorf("invalid addr %q", addr)
}
hostBus := addrParts[0]
hostAddr := addrParts[1]
hostPort := ""
if len(addrParts) == 3 {
hostPort = addrParts[2]
}
return getUSBDevQemuOptions(vendorId, productId, hostBus, hostAddr, hostPort)
}
func (dev *sUSBDevice) GetKernelDriver() (string, error) {
return "", nil
}
func (dev *sUSBDevice) GetQemuId() string {
addrParts := strings.Split(dev.dev.Addr, ":")
return GetUSBDevId(dev.dev.VendorId, dev.dev.DeviceId, addrParts[0], addrParts[1])
}
func (dev *sUSBDevice) GetPassthroughOptions() map[string]string {
opts, _ := GetUSBDevQemuOptions(dev.dev.GetVendorDeviceId(), dev.dev.Addr)
return opts
}
func (dev *sUSBDevice) GetPassthroughCmd(index int) string {
opts, _ := GetUSBDevQemuOptions(dev.dev.GetVendorDeviceId(), dev.dev.Addr)
optsStr := []string{}
for k, v := range opts {
optsStr = append(optsStr, fmt.Sprintf("%s=%s", k, v))
}
opt := fmt.Sprintf(" -device usb-host,%s", strings.Join(optsStr, ","))
return opt
}
func (dev *sUSBDevice) GetHotPlugOptions(isolatedDev *desc.SGuestIsolatedDevice, guestDesc *desc.SGuestDesc) ([]*HotPlugOption, error) {
opts, err := GetUSBDevQemuOptions(dev.dev.GetVendorDeviceId(), dev.dev.Addr)
if err != nil {
return nil, errors.Wrap(err, "GetUSBDevQemuOptions")
}
opts["id"] = isolatedDev.Usb.Id
opts["bus"] = fmt.Sprintf("%s.0", guestDesc.Usb.Id)
return []*HotPlugOption{
{
Device: "usb-host",
Options: opts,
},
}, nil
}
func (dev *sUSBDevice) GetHotUnplugOptions(*desc.SGuestIsolatedDevice) ([]*HotUnplugOption, error) {
return []*HotUnplugOption{
{Id: dev.GetQemuId()},
}, nil
}
func getPassthroughUSBs() ([]*sUSBDevice, error) {
ret, err := bashOutput("lsusb")
if err != nil {
return nil, errors.Wrap(err, "execute lsusb")
}
lines := []string{}
for _, l := range ret {
if len(l) != 0 {
lines = append(lines, l)
}
}
devs, err := parseLsusb(lines)
if err != nil {
return nil, errors.Wrap(err, "parseLsusb")
}
treeRet, err := bashRawOutput("lsusb -t")
if err != nil {
return nil, errors.Wrap(err, "execute `lsusb -t`")
}
trees, err := parseLsusbTrees(treeRet)
if err != nil {
return nil, errors.Wrap(err, "parseLsusbTrees")
}
// fitler linux root hub
retDev := make([]*sUSBDevice, 0)
for _, dev := range devs {
// REF: https://github.com/virt-manager/virt-manager/blob/0038d750c9056ddd63cb48b343e451f8db2746fa/virtinst/nodedev.py#L142
if isUSBLinuxRootHub(dev.dev.VendorId, dev.dev.DeviceId) {
continue
}
// check by trees
isHubClass, err := checkIsUSBHubClassAndSetPortPath(dev, trees)
if err != nil {
return nil, errors.Wrap(err, "check isUSBHubClass")
}
if isHubClass {
continue
}
retDev = append(retDev, dev)
}
return retDev, nil
}
func isUSBLinuxRootHub(vendorId string, deviceId string) bool {
if vendorId == "1d6b" && utils.IsInStringArray(deviceId, []string{"0001", "0002", "0003"}) {
return true
}
return false
}
func checkIsUSBHubClassAndSetPortPath(dev *sUSBDevice, trees *sLsusbTrees) (bool, error) {
busNum, err := dev.lsusbLine.GetBusNumber()
if err != nil {
return false, errors.Wrapf(err, "GetBusNumber of dev %#v", dev.lsusbLine)
}
devNum, err := dev.lsusbLine.GetDeviceNumber()
if err != nil {
return false, errors.Wrapf(err, "GetDeviceNumber of dev %#v", dev.lsusbLine)
}
tree, ok := trees.GetBus(busNum)
if !ok {
return false, errors.Errorf("not found dev %#v by bus %d", dev.lsusbLine, busNum)
}
treeDev := tree.GetDevice(devNum)
if treeDev == nil {
return false, errors.Errorf("not found dev %#v by bus %d, dev %d", dev.lsusbLine, busNum, devNum)
}
if utils.IsInStringArray(treeDev.Class, []string{"root_hub", "Hub"}) {
return true, nil
}
dev.SetPortPath(tree.GetPortPath(devNum))
return false, nil
}
func parseLsusb(lines []string) ([]*sUSBDevice, error) {
devs := make([]*sUSBDevice, 0)
for _, line := range lines {
if len(line) == 0 {
continue
}
dev, err := parseLsusbLine(line)
if err != nil {
return nil, errors.Wrapf(err, "parseLsusbLine %q", line)
}
usbDev := newUSBDevice(dev.ToPCIDevice(), dev)
devs = append(devs, usbDev)
}
return devs, nil
}
var (
lsusbRegex = `^Bus (?P<bus_id>([0-9]{3})) Device (?P<device>([0-9]{3})): ID (?P<vendor_id>([0-9a-z]{4})):(?P<device_id>([0-9a-z]{4}))\s{0,1}(?P<name>(.*))`
)
type sLsusbLine struct {
BusId string `json:"bus_id"`
Device string `json:"device"`
VendorId string `json:"vendor_id"`
DeviceId string `json:"device_id"`
Name string `json:"name"`
}
func parseLsusbLine(line string) (*sLsusbLine, error) {
ret := regutils2.SubGroupMatch(lsusbRegex, line)
dev := new(sLsusbLine)
if err := jsonutils.Marshal(ret).Unmarshal(dev); err != nil {
return nil, err
}
return dev, nil
}
func (dev *sLsusbLine) ToPCIDevice() *PCIDevice {
return &PCIDevice{
Addr: fmt.Sprintf("%s:%s", dev.BusId, dev.Device),
VendorId: dev.VendorId,
DeviceId: dev.DeviceId,
ModelName: dev.Name,
}
}
func (dev *sLsusbLine) GetBusNumber() (int, error) {
return strconv.Atoi(dev.BusId)
}
func (dev *sLsusbLine) GetDeviceNumber() (int, error) {
return strconv.Atoi(dev.Device)
}
type sLsusbTrees struct {
Trees map[int]*sLsusbTree
sorted bool
sortedTrees sortLsusbTree
}
type sortLsusbTree []*sLsusbTree
func (t sortLsusbTree) Len() int {
return len(t)
}
func (t sortLsusbTree) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
func (t sortLsusbTree) Less(i, j int) bool {
it := t[i]
jt := t[j]
return it.Bus < jt.Bus
}
func newLsusbTrees() *sLsusbTrees {
return &sLsusbTrees{
Trees: make(map[int]*sLsusbTree),
sorted: false,
sortedTrees: sortLsusbTree([]*sLsusbTree{}),
}
}
func (ts *sLsusbTrees) Add(bus int, tree *sLsusbTree) *sLsusbTrees {
ts.Trees[bus] = tree
return ts
}
func (ts *sLsusbTrees) sortTrees() *sLsusbTrees {
if ts.sorted {
return ts
}
for _, t := range ts.Trees {
ts.sortedTrees = append(ts.sortedTrees, t)
}
sort.Sort(ts.sortedTrees)
return ts
}
func (ts *sLsusbTrees) GetContent() string {
ts.sortTrees()
ret := []string{}
for _, t := range ts.sortedTrees {
ret = append(ret, t.GetContents()...)
}
return strings.Join(ret, "\n")
}
func (ts *sLsusbTrees) GetBus(bus int) (*sLsusbTree, bool) {
t, ok := ts.Trees[bus]
return t, ok
}
// parseLsusbTrees parses `lsusb -t` output
func parseLsusbTrees(lines []string) (*sLsusbTrees, error) {
return _parseLsusbTrees(lines)
}
func _parseLsusbTrees(lines []string) (*sLsusbTrees, error) {
trees := newLsusbTrees()
var (
prevTree *sLsusbTree
)
for idx, line := range lines {
if len(strings.TrimSpace(line)) == 0 {
continue
}
tree, err := newLsusbTreeByLine(line)
if err != nil {
return nil, errors.Wrapf(err, "by line %q, index %d", line, idx)
}
if tree.IsRootBus {
tree.parentNode = nil
trees.Add(tree.Bus, tree)
prevTree = tree
} else {
if len(tree.LinePrefix) > len(prevTree.LinePrefix) {
// child node should be added to previous node
tree.parentNode = prevTree
tree.parentNode.AddNode(tree)
} else if len(tree.LinePrefix) == len(prevTree.LinePrefix) {
// sibling node should be added to parent
prevTree.parentNode.AddNode(tree)
} else if len(tree.LinePrefix) < len(prevTree.LinePrefix) {
// find current node's sibling node and added to it's parent
parent := prevTree.FindParentByTree(tree)
if parent == nil {
return nil, errors.Errorf("can't found parent by tree %s, current line %q, prevTree %s", jsonutils.Marshal(tree), line, jsonutils.Marshal(prevTree))
}
parent.AddNode(tree)
}
prevTree = tree
}
}
return trees, nil
}
const (
busRootPrefix = "/: Bus"
)
var (
lsusbTreeRootBusRegex = `(?P<prefix>(.*))Bus (?P<bus_id>([0-9]{1,3}))\.`
lsusbTreeBusSuffixRegex = `Port (?P<port_id>([0-9]{1,3})): Dev (?P<device>([0-9]{1,3})), Class=(?P<class>(.*)), Driver=(?P<driver>(.*)),\s{0,1}(?P<speed>(.*))`
lsusbTreeSuffixRegex = `Port (?P<port_id>([0-9]{1,3})): Dev (?P<device>([0-9]{1,3})), If (?P<interface>([0-9]{1,2})), Class=(?P<class>(.*)), Driver=(?P<driver>(.*)),\s{0,1}(?P<speed>(.*))`
lsusbTreeRootBusLineRegex = lsusbTreeRootBusRegex + lsusbTreeBusSuffixRegex
lsusbTreeLineRegex = `(?P<prefix>(.*))` + lsusbTreeSuffixRegex
)
type sLsusbTree struct {
parentNode *sLsusbTree
IsRootBus bool `json:"is_root_bus"`
LinePrefix string `json:"line_prefix"`
Bus int `json:"bus"`
Port int `json:"port"`
Dev int `json:"dev"`
// If maybe nil
If int `json:"if"`
Class string `json:"class"`
Driver string `json:"driver"`
Content string `json:"content"`
Nodes []*sLsusbTree `json:"nodes"`
}
func newLsusbTreeByLine(line string) (*sLsusbTree, error) {
var (
isRootBus = false
regExp = lsusbTreeLineRegex
)
if strings.HasPrefix(line, busRootPrefix) {
isRootBus = true
}
if isRootBus {
regExp = lsusbTreeRootBusLineRegex
}
ret := regutils2.SubGroupMatch(regExp, line)
linePrefix := ret["prefix"]
if linePrefix == "" {
return nil, errors.Errorf("not found prefix of line %q", line)
}
t := &sLsusbTree{
IsRootBus: isRootBus,
Content: line,
LinePrefix: linePrefix,
Nodes: make([]*sLsusbTree, 0),
}
if isRootBus {
// parse bus
busIdStr, ok := ret["bus_id"]
if !ok {
return nil, errors.Errorf("not found 'Bus' in %q", line)
}
busId, err := strconv.Atoi(busIdStr)
if err != nil {
return nil, errors.Errorf("invalid Bus string %q", busIdStr)
}
t.Bus = busId
}
// parse port
portIdStr, ok := ret["port_id"]
if !ok {
return nil, errors.Errorf("not found 'Port' in %q", line)
}
portId, err := strconv.Atoi(portIdStr)
if err != nil {
return nil, errors.Errorf("invalid Port string %q", portIdStr)
}
t.Port = portId
// parse dev
devStr, ok := ret["device"]
if !ok {
return nil, errors.Errorf("not found 'Dev' in %q", line)
}
dev, err := strconv.Atoi(devStr)
if err != nil {
return nil, errors.Errorf("invalid Dev string %q", devStr)
}
t.Dev = dev
// parse if when not root bus
if !isRootBus {
ifStr, ok := ret["interface"]
if !ok {
return nil, errors.Errorf("not found 'If' in %q", line)
}
ifN, err := strconv.Atoi(ifStr)
if err != nil {
return nil, errors.Errorf("invalid ifStr string %q", ifStr)
}
t.If = ifN
}
// parse class
class, ok := ret["class"]
if !ok {
return nil, errors.Errorf("not found 'Class' in %q", line)
}
t.Class = class
// parse driver
driver := ret["driver"]
t.Driver = driver
return t, nil
}
func (t *sLsusbTree) AddNode(child *sLsusbTree) *sLsusbTree {
child.Bus = t.Bus
child.parentNode = t
t.Nodes = append(t.Nodes, child)
return t
}
func (t *sLsusbTree) FindParentByTree(it *sLsusbTree) *sLsusbTree {
tl := len(t.LinePrefix)
itl := len(it.LinePrefix)
if tl == itl {
return t.parentNode
} else if tl > itl {
return t
} else {
return t.parentNode.FindParentByTree(it)
}
}
func (t *sLsusbTree) GetContents() []string {
ret := []string{t.Content}
for _, n := range t.Nodes {
ret = append(ret, n.GetContents()...)
}
return ret
}
func (t *sLsusbTree) GetPortPath(devNum int) string {
portPath, found := t.GetDevicePortPath("", devNum)
if !found {
return ""
}
return portPath
}
func (t *sLsusbTree) GetDevicePortPath(portPath string, devNum int) (string, bool) {
if !t.IsRootBus {
if len(portPath) > 0 {
portPath = fmt.Sprintf("%s.%d", portPath, t.Port)
} else {
portPath = strconv.Itoa(t.Port)
}
}
if t.Dev == devNum {
return portPath, true
}
for _, node := range t.Nodes {
devPortPath, found := node.GetDevicePortPath(portPath, devNum)
if found {
return devPortPath, true
}
}
return "", false
}
func (t *sLsusbTree) GetDevice(devNum int) *sLsusbTree {
// should check self firstly
if t.Dev == devNum {
return t
}
// then check children
for _, node := range t.Nodes {
dev := node.GetDevice(devNum)
if dev != nil {
return dev
}
}
return nil
}