mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
587 lines
14 KiB
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
|
|
}
|