| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- // 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
- }
- // TODO: rename PCIDevice
- func newUSBDevice(dev *PCIDevice, lsusbLine *sLsusbLine) *sUSBDevice {
- return &sUSBDevice{
- SBaseDevice: NewBaseDevice(dev, api.USB_TYPE),
- lsusbLine: lsusbLine,
- }
- }
- 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 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)
- }
- 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 {
- return nil, fmt.Errorf("invalid addr %q", addr)
- }
- hostBus := addrParts[0]
- hostAddr := addrParts[1]
- return getUSBDevQemuOptions(vendorId, productId, hostBus, hostAddr)
- }
- 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 := isUSBHubClass(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 isUSBHubClass(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)
- }
- return utils.IsInStringArray(treeDev.Class, []string{"root_hub", "Hub"}), 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]{2}))\.`
- lsusbTreeBusSuffixRegex = `Port (?P<port_id>([0-9]{1,2})): Dev (?P<device>([0-9]{1,2})), Class=(?P<class>(.*)), Driver=(?P<driver>(.*)),\s{0,1}(?P<speed>(.*))`
- lsusbTreeSuffixRegex = `Port (?P<port_id>([0-9]{1,2})): Dev (?P<device>([0-9]{1,2})), 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) 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
- }
|