gpu.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package isolated_device
  15. import (
  16. "fmt"
  17. "os"
  18. "os/exec"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. "syscall"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/util/sets"
  27. "yunion.io/x/pkg/utils"
  28. api "yunion.io/x/onecloud/pkg/apis/compute"
  29. o "yunion.io/x/onecloud/pkg/hostman/options"
  30. "yunion.io/x/onecloud/pkg/util/fileutils2"
  31. "yunion.io/x/onecloud/pkg/util/procutils"
  32. "yunion.io/x/onecloud/pkg/util/regutils2"
  33. "yunion.io/x/onecloud/pkg/util/sysutils"
  34. )
  35. const (
  36. CLASS_CODE_VGA = "0300"
  37. CLASS_CODE_3D = "0302"
  38. CLASS_CODE_DISP = "0380"
  39. )
  40. var (
  41. GpuClassCodes = []string{
  42. CLASS_CODE_VGA,
  43. CLASS_CODE_3D,
  44. CLASS_CODE_DISP,
  45. }
  46. )
  47. const (
  48. BUSID_REGEX = `[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]`
  49. CODE_REGEX = `[0-9a-fA-F]{4}`
  50. LABEL_REGEX = `[\w+\ \.\,\:\+\&\-\/\[\]\(\)]+`
  51. VFIO_PCI_KERNEL_DRIVER = "vfio-pci"
  52. DEFAULT_VGA_CMD = " -vga std"
  53. // 在qemu/kvm下模拟Windows Hyper-V的一些半虚拟化特性,以便更好地使用Win虚拟机
  54. // http://blog.wikichoon.com/2014/07/enabling-hyper-v-enlightenments-with-kvm.html
  55. // 但实际测试不行,虚拟机不能运行nvidia驱动
  56. // DEFAULT_CPU_CMD = "host,kvm=off,hv_relaxed,hv_spinlocks=0x1fff,hv_vapic,hv_time"
  57. DEFAULT_CPU_CMD = "host,kvm=off"
  58. )
  59. func isInWhitelistModels(models []IsolatedDeviceModel, dev *PCIDevice) bool {
  60. for _, model := range models {
  61. if model.VendorId == dev.VendorId && model.DeviceId == dev.DeviceId {
  62. return true
  63. }
  64. }
  65. return false
  66. }
  67. func getPassthroughGPUs(filteredAddrs []string, enableWhitelist bool, whitelistModels []IsolatedDeviceModel) ([]*PCIDevice, error, []error) {
  68. lines, err := getGPUPCIStr()
  69. if err != nil {
  70. return nil, err, nil
  71. }
  72. warns := make([]error, 0)
  73. devs := []*PCIDevice{}
  74. log.Infof("filter address %v, enableWhiteList: %v", filteredAddrs, enableWhitelist)
  75. for _, line := range lines {
  76. if len(line) == 0 {
  77. continue
  78. }
  79. dev := NewPCIDevice2(line)
  80. if utils.IsInStringArray(dev.Addr, filteredAddrs) {
  81. continue
  82. }
  83. if !utils.IsInArray(dev.ClassCode, GpuClassCodes) {
  84. continue
  85. }
  86. if o.HostOptions.BootVgaPciAddr != "" {
  87. if dev.Addr == o.HostOptions.BootVgaPciAddr && !o.HostOptions.UseBootVga {
  88. continue
  89. }
  90. } else {
  91. if ok, err := dev.IsBootVGA(); err != nil {
  92. return nil, err, nil
  93. } else if ok && !o.HostOptions.UseBootVga {
  94. continue
  95. }
  96. }
  97. if dev.ClassCode == CLASS_CODE_DISP && !utils.IsInStringArray(dev.VendorId, []string{api.NVIDIA_VENDOR_ID, api.AMD_VENDOR_ID}) {
  98. log.Infof("Skip add device %s vendor is unsupport", dev.Addr)
  99. continue
  100. }
  101. if enableWhitelist {
  102. if !isInWhitelistModels(whitelistModels, dev) {
  103. log.Infof("skip add device %s cause of not in isolated_device_models", dev.String())
  104. continue
  105. }
  106. }
  107. if err := dev.checkSameIOMMUGroupDevice(); err != nil {
  108. warns = append(warns, errors.Wrapf(err, "get dev %s iommu group devices", dev.Addr))
  109. continue
  110. }
  111. if isBootVga, err := dev.IsBootVGA(); err != nil {
  112. warns = append(warns, errors.Wrapf(err, "check dev %s is boot vga devices", dev.Addr))
  113. continue
  114. } else if isBootVga && !o.HostOptions.UseBootVga {
  115. log.Infof("skip boot vga device %s", dev.Addr)
  116. continue
  117. }
  118. if err := dev.forceBindVFIOPCIDriver(o.HostOptions.UseBootVga, o.HostOptions.BootVgaPciAddr); err != nil {
  119. warns = append(warns, errors.Wrapf(err, "force bind vfio-pci driver %s", dev.Addr))
  120. continue
  121. }
  122. devs = append(devs, dev)
  123. }
  124. ret := []*PCIDevice{}
  125. for _, dev := range devs {
  126. if drv, err := dev.getKernelDriver(); err != nil {
  127. log.Errorf("Device %s get kernel driver error: %s", dev.Addr, err.Error())
  128. warns = append(warns, fmt.Errorf("Device %s get kernel driver error: %s", dev.Addr, err.Error()))
  129. } else if drv == "" || drv == VFIO_PCI_KERNEL_DRIVER {
  130. ret = append(ret, dev)
  131. } else {
  132. log.Warningf("GPU %v use kernel driver %q, skip it", dev, drv)
  133. warns = append(warns, fmt.Errorf("GPU %s use kernel driver %s, skip it", dev.Addr, drv))
  134. }
  135. }
  136. return ret, nil, warns
  137. }
  138. func GetPCIStrByAddr(addr string) ([]string, error) {
  139. cmd := "lspci -nnmm"
  140. if addr != "" {
  141. cmd = fmt.Sprintf("%s -s %s", cmd, addr)
  142. }
  143. ret, err := bashOutput(cmd)
  144. if err != nil {
  145. return nil, err
  146. }
  147. lines := []string{}
  148. for _, l := range ret {
  149. if len(l) != 0 {
  150. lines = append(lines, l)
  151. }
  152. }
  153. return lines, err
  154. }
  155. func getGPUPCIStr() ([]string, error) {
  156. return GetPCIStrByAddr("")
  157. }
  158. type IExecutor interface {
  159. RunCmd(cmd string) ([]string, error)
  160. }
  161. var defaultExecutor IExecutor = new(SDefaultExecutor)
  162. func GetDefaultExecutor() IExecutor {
  163. return defaultExecutor
  164. }
  165. type SDefaultExecutor struct{}
  166. func (*SDefaultExecutor) RunCmd(cmd string) ([]string, error) {
  167. return bashOutput(cmd)
  168. }
  169. type PCIDevice struct {
  170. Addr string `json:"bus_id"`
  171. ClassName string `json:"class_name"`
  172. ClassCode string `json:"class_code"`
  173. VendorName string `json:"vendor_name"`
  174. VendorId string `json:"vendor_id"`
  175. DeviceName string `json:"device_name"`
  176. DeviceId string `json:"device_id"`
  177. SubvendorName string `json:"subvendor_name"`
  178. SubvendorId string `json:"subvendor_id"`
  179. SubdeviceName string `json:"subdevice_name"`
  180. SubdeviceId string `json:"subdevice_id"`
  181. ModelName string `json:"model_name"`
  182. RestIOMMUGroupDevs []*PCIDevice `json:"-"`
  183. PCIEInfo *api.IsolatedDevicePCIEInfo `json:"pcie_info"`
  184. }
  185. func NewPCIDevice(addr string, executors ...IExecutor) (*PCIDevice, error) {
  186. if len(addr) == 0 {
  187. return nil, errors.Errorf("input line is empty")
  188. }
  189. var executor IExecutor
  190. if len(executors) == 0 {
  191. executor = GetDefaultExecutor()
  192. } else {
  193. executor = executors[0]
  194. }
  195. ret, err := executor.RunCmd(fmt.Sprintf("lspci -nnmm -s %s", addr))
  196. if err != nil {
  197. return nil, errors.Wrapf(err, "run lspci -nnmm -s %s", addr)
  198. }
  199. dev := NewPCIDevice2(strings.Join(ret, ""))
  200. if err := dev.checkSameIOMMUGroupDevice(); err != nil {
  201. return nil, err
  202. }
  203. if err := dev.forceBindVFIOPCIDriver(o.HostOptions.UseBootVga, o.HostOptions.BootVgaPciAddr); err != nil {
  204. return nil, fmt.Errorf("Force bind vfio-pci driver: %v", err)
  205. }
  206. return dev, nil
  207. }
  208. func NewPCIDevice2(line string, executors ...IExecutor) *PCIDevice {
  209. var executor IExecutor
  210. if len(executors) == 0 {
  211. executor = GetDefaultExecutor()
  212. } else {
  213. executor = executors[0]
  214. }
  215. dev := parseLspci(line)
  216. if err := dev.fillPCIEInfo(executor); err != nil {
  217. log.Warningf("fillPCIEInfo for line: %q, device: %s, error: %v", line, dev.String(), err)
  218. }
  219. return dev
  220. }
  221. type sGPUBaseDevice struct {
  222. *SBaseDevice
  223. }
  224. func newGPUBaseDevice(dev *PCIDevice, devType string) *sGPUBaseDevice {
  225. return &sGPUBaseDevice{
  226. SBaseDevice: NewBaseDevice(dev, devType),
  227. }
  228. }
  229. func (dev *sGPUBaseDevice) GetCPUCmd() string {
  230. return DEFAULT_CPU_CMD
  231. }
  232. func (dev *sGPUBaseDevice) GetVGACmd() string {
  233. return DEFAULT_VGA_CMD
  234. }
  235. func (dev *sGPUBaseDevice) DetectByAddr() error {
  236. _, err := detectPCIDevByAddr(dev.GetAddr())
  237. return err
  238. }
  239. func (dev *sGPUBaseDevice) CustomProbe(idx int) error {
  240. // check environments on first probe
  241. if idx == 0 {
  242. // vfio kernel driver check
  243. for _, driver := range []string{"vfio", "vfio_iommu_type1", "vfio-pci"} {
  244. if err := procutils.NewRemoteCommandAsFarAsPossible("modprobe", driver).Run(); err != nil {
  245. return fmt.Errorf("modprobe %s: %v", driver, err)
  246. }
  247. }
  248. // grub check
  249. grubCmdline, err := fileutils2.FileGetContents("/proc/cmdline")
  250. if err != nil {
  251. return err
  252. }
  253. grubCmdline = strings.TrimSpace(grubCmdline)
  254. params := sets.NewString(strings.Split(grubCmdline, " ")...)
  255. if !params.IsSuperset(sets.NewString("vfio_iommu_type1.allow_unsafe_interrupts=1")) {
  256. return fmt.Errorf("GRUB_CMDLINE iommu parameters vfio_iommu_type1.allow_unsafe_interrupts=1 missing")
  257. }
  258. if sysutils.IsProcessorIntel() && !params.IsSuperset(sets.NewString("intel_iommu=on")) {
  259. return fmt.Errorf("GRUB_CMDLINE iommu parameters intel_iommu=on missing")
  260. }
  261. isNouveauBlacklisted := false
  262. if params.IsSuperset(sets.NewString("rdblacklist=nouveau", "nouveau.modeset=0")) ||
  263. params.IsSuperset(sets.NewString("rd.driver.blacklist=nouveau", "nouveau.modeset=0")) {
  264. isNouveauBlacklisted = true
  265. }
  266. if !isNouveauBlacklisted {
  267. return fmt.Errorf("Some GRUB_CMDLINE nouveau_blacklisted parameters are missing")
  268. }
  269. }
  270. driver, err := dev.GetKernelDriver()
  271. if err != nil {
  272. return err
  273. }
  274. if driver != "" && driver != VFIO_PCI_KERNEL_DRIVER {
  275. return fmt.Errorf("GPU is occupied by another driver: %s", driver)
  276. }
  277. if driver == "" {
  278. //fileutils2.FilePutContents(
  279. //fmt.Sprintf("%s\n", strings.Replace(dev.GetVendorDeviceId(), ":", " ", -1)),
  280. //false)
  281. }
  282. return nil
  283. }
  284. func (dev *sGPUBaseDevice) GetQemuId() string {
  285. return fmt.Sprintf("dev_%s", strings.ReplaceAll(dev.GetAddr(), ":", "_"))
  286. }
  287. func getGuestAddr(index int) string {
  288. vAddr := fmt.Sprintf("0x%x", 21+index) // from 0x15 above
  289. return vAddr
  290. }
  291. type sGPUHPCDevice struct {
  292. *sGPUBaseDevice
  293. }
  294. func NewGPUHPCDevice(dev *PCIDevice) *sGPUHPCDevice {
  295. gpuDev := &sGPUHPCDevice{
  296. sGPUBaseDevice: newGPUBaseDevice(dev, api.GPU_HPC_TYPE),
  297. }
  298. return gpuDev
  299. }
  300. func (gpu *sGPUHPCDevice) GetPassthroughCmd(index int) string {
  301. // vAddr := getGuestAddr(index)
  302. return fmt.Sprintf(" -device vfio-pci,host=%s,multifunction=on", gpu.GetAddr())
  303. }
  304. // parseLspci parse one line output of `lspci -nnmm`
  305. func parseLspci(line string) *PCIDevice {
  306. itemRegex := `(?P<bus_id>(` + BUSID_REGEX + `))` +
  307. `\ "(?P<class_name>` + LABEL_REGEX + `)\ \[(?P<class_code>` + CODE_REGEX + `)\]"` +
  308. `\ "(?P<vendor_name>` + LABEL_REGEX + `)\ \[(?P<vendor_id>` + CODE_REGEX + `)\]"` +
  309. `\ "(?P<device_name>` + LABEL_REGEX + `)\ \[(?P<device_id>` + CODE_REGEX + `)\]"` +
  310. `\ .*\"((?P<subvendor_name>` + LABEL_REGEX + `)\ \[(?P<subvendor_id>` + CODE_REGEX + `)\])*"` +
  311. `\ "((?P<subdevice_name>` + LABEL_REGEX + `)\ \[(?P<subdevice_id>` + CODE_REGEX + `)\])*`
  312. ret := regutils2.SubGroupMatch(itemRegex, line)
  313. dev := PCIDevice{}
  314. jsonutils.Marshal(ret).Unmarshal(&dev)
  315. deviceRegex := `(?P<code_name>` + LABEL_REGEX + `)\ \[(?P<model_name>` + LABEL_REGEX + `)\]`
  316. if ret := regutils2.SubGroupMatch(deviceRegex, dev.DeviceName); len(ret) != 0 {
  317. dev.ModelName = ret["model_name"]
  318. }
  319. return &dev
  320. }
  321. func (d *PCIDevice) GetVendorDeviceId() string {
  322. return fmt.Sprintf("%s:%s", d.VendorId, d.DeviceId)
  323. }
  324. // checkSameIOMMUGroupDevice checks related device like Audio in same iommu group
  325. // e.g.
  326. // 41:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP107 [GeForce GTX 1050 Ti] [10de:1c82] (rev a1)
  327. // 41:00.1 Audio device [0403]: NVIDIA Corporation GP107GL High Definition Audio Controller [10de:0fb9] (rev a1)
  328. func (d *PCIDevice) checkSameIOMMUGroupDevice() error {
  329. group, err := NewIOMMUGroup()
  330. if err != nil {
  331. return fmt.Errorf("IOMMUGroup FindSameGroupDevs: %v", err)
  332. }
  333. d.RestIOMMUGroupDevs = group.FindSameGroupDevs(d.Addr, d.VendorId)
  334. return nil
  335. }
  336. func (d *PCIDevice) IsBootVGA() (bool, error) {
  337. addr := d.Addr
  338. if addr == "" {
  339. log.Warningf("device address is empty: %#v", d)
  340. return false, nil
  341. }
  342. output, err := procutils.NewCommand("find", "/sys/devices", "-name", "boot_vga").Output()
  343. if err != nil {
  344. if exiterr, ok := err.(*exec.ExitError); ok {
  345. if code, ok := exiterr.Sys().(syscall.WaitStatus); ok &&
  346. code.ExitStatus() == 1 && strings.Contains(string(output), "No such file or directory") {
  347. log.Warningf("find boot vga %s", output)
  348. } else {
  349. return false, err
  350. }
  351. } else {
  352. return false, err
  353. }
  354. }
  355. paths := ParseOutput(output, true)
  356. for _, p := range paths {
  357. if strings.Contains(p, addr) && !strings.Contains(p, "No such file or directory") {
  358. if content, err := fileutils2.FileGetContents(p); err != nil {
  359. return false, err
  360. } else {
  361. if len(content) > 0 && strings.HasPrefix(content, "1") {
  362. log.Infof("PCI address %s is boot_vga: %s", addr, p)
  363. return true, nil
  364. }
  365. }
  366. }
  367. }
  368. return false, nil
  369. }
  370. func (d *PCIDevice) forceBindVFIOPCIDriver(useBootVGA bool, bootVgaPciAddr string) error {
  371. if !utils.IsInStringArray(d.ClassCode, []string{CLASS_CODE_VGA, CLASS_CODE_3D}) {
  372. return nil
  373. }
  374. if !useBootVGA && bootVgaPciAddr != "" {
  375. if d.Addr == bootVgaPciAddr {
  376. log.Infof("device %#v is specific boot vga addr, skip it", d)
  377. return nil
  378. }
  379. } else {
  380. isBootVGA, err := d.IsBootVGA()
  381. if err != nil {
  382. return err
  383. }
  384. if !useBootVGA && isBootVGA {
  385. log.Infof("%#v is boot vga card, skip it", d)
  386. return nil
  387. }
  388. }
  389. if d.IsVFIOPCIDriverUsed() {
  390. log.Infof("%s already use vfio-pci driver", d)
  391. return nil
  392. }
  393. devs := []*PCIDevice{}
  394. devs = append(devs, d.RestIOMMUGroupDevs...)
  395. devs = append(devs, d)
  396. for _, dev := range devs {
  397. if dev.IsVFIOPCIDriverUsed() {
  398. log.Infof("%s already use vfio-pci driver", d.Addr)
  399. continue
  400. }
  401. if err := dev.bindAddrVFIOPCI(); err != nil {
  402. return fmt.Errorf("bind %s vfio-pci driver: %v", dev, err)
  403. }
  404. }
  405. return nil
  406. }
  407. func (d *PCIDevice) bindAddrVFIOPCI() error {
  408. if err := d.unbindDriver(); err != nil {
  409. return fmt.Errorf("unbindDriver: %v", err)
  410. }
  411. if err := d.bindDriver(); err != nil {
  412. return fmt.Errorf("bindDriver: %v", err)
  413. }
  414. return nil
  415. }
  416. func (d *PCIDevice) unbindDriver() error {
  417. driver, err := d.getKernelDriver()
  418. if err != nil {
  419. return err
  420. }
  421. if len(driver) != 0 {
  422. if err := fileutils2.FilePutContents(
  423. fmt.Sprintf("/sys/bus/pci/devices/0000:%s/driver/unbind", d.Addr),
  424. fmt.Sprintf("0000:%s", d.Addr), false); err != nil {
  425. return fmt.Errorf("unbindDriver: %v", err)
  426. }
  427. }
  428. return nil
  429. }
  430. func (d *PCIDevice) bindDriver() error {
  431. vendorDevId := fmt.Sprintf("%s %s", d.VendorId, d.DeviceId)
  432. err := fileutils2.FilePutContents(
  433. "/sys/bus/pci/drivers/vfio-pci/new_id",
  434. fmt.Sprintf("%s\n", vendorDevId),
  435. false,
  436. )
  437. if err != nil {
  438. log.Errorf("failed write %s to %s, try bind addr", vendorDevId, "/sys/bus/pci/drivers/vfio-pci/new_id")
  439. } else {
  440. return nil
  441. }
  442. if err := fileutils2.FilePutContents("/sys/bus/pci/drivers/vfio-pci/bind", fmt.Sprintf("0000:%s", d.Addr), false); err != nil {
  443. return fmt.Errorf("bind driver %s : %v", d.Addr, err)
  444. }
  445. return nil
  446. }
  447. func (d *PCIDevice) fillPCIEInfo(executor IExecutor) error {
  448. if d.Addr == "" {
  449. return errors.Errorf("device address is empty: %s", d.String())
  450. }
  451. cmd := fmt.Sprintf("lspci -vvv -s %s", d.Addr)
  452. lines, err := executor.RunCmd(cmd)
  453. if err != nil {
  454. return errors.Wrapf(err, "execute cmd: %s", cmd)
  455. }
  456. linkCapKey := "LnkCap:"
  457. for _, line := range lines {
  458. if strings.Contains(line, linkCapKey) {
  459. info, err := parsePCIELinkCap(line)
  460. if err != nil {
  461. return errors.Wrapf(err, "parsePCIELinkCap")
  462. }
  463. d.PCIEInfo = info
  464. return nil
  465. }
  466. }
  467. return nil
  468. }
  469. func parsePCIELinkCap(line string) (*api.IsolatedDevicePCIEInfo, error) {
  470. // e.g. parse following line
  471. // LnkCap: Port #0, Speed 8GT/s, Width x16, ASPM L0s L1, Exit Latency L0s <1us, L1 <4us
  472. lnkCapExp := `\s*LnkCap:.*Speed\s(?P<speed>((\d*[.])?\d+GT/s)),\sWidth\sx(?P<lane_width>(\d{1,})),.*`
  473. ret := regutils2.SubGroupMatch(lnkCapExp, line)
  474. if len(ret) == 0 {
  475. return nil, errors.Errorf("can't parse line: %q", line)
  476. }
  477. laneWidthStr := ret["lane_width"]
  478. laneWidth, _ := strconv.Atoi(laneWidthStr)
  479. return api.NewIsolatedDevicePCIEInfo(ret["speed"], laneWidth)
  480. }
  481. func (d *PCIDevice) String() string {
  482. return jsonutils.Marshal(d).String()
  483. }
  484. func (d *PCIDevice) IsVFIOPCIDriverUsed() bool {
  485. driver, _ := d.getKernelDriver()
  486. if driver != VFIO_PCI_KERNEL_DRIVER {
  487. return false
  488. }
  489. for _, dev := range d.RestIOMMUGroupDevs {
  490. driver, _ := dev.getKernelDriver()
  491. if driver != VFIO_PCI_KERNEL_DRIVER {
  492. return false
  493. }
  494. }
  495. return true
  496. }
  497. func (d *PCIDevice) getKernelDriver() (string, error) {
  498. prompt := "Kernel driver in use: "
  499. lines, err := bashOutput(fmt.Sprintf("lspci -k -s %s", d.Addr))
  500. if err != nil {
  501. return "", err
  502. }
  503. for _, line := range lines {
  504. begin := strings.Index(line, prompt)
  505. if begin >= 0 {
  506. end := begin + len(prompt)
  507. return line[end:], nil
  508. }
  509. }
  510. // no driver in use
  511. return "", nil
  512. }
  513. type IOMMUGroup struct {
  514. // busId: group
  515. group map[string]string
  516. }
  517. func NewIOMMUGroup() (*IOMMUGroup, error) {
  518. devPaths := "/sys/kernel/iommu_groups/"
  519. dict := make(map[string]string)
  520. err := filepath.Walk(devPaths, func(path string, info os.FileInfo, err error) error {
  521. if err != nil {
  522. return err
  523. }
  524. if info.IsDir() {
  525. return nil
  526. }
  527. parts := strings.Split(path, "/")
  528. group := parts[4]
  529. busId := parts[len(parts)-1]
  530. dict[busId] = group
  531. return nil
  532. })
  533. if err != nil {
  534. return nil, err
  535. }
  536. return &IOMMUGroup{group: dict}, nil
  537. }
  538. func (g *IOMMUGroup) ListDevices(groupNum, selfAddr, vendorId string) []*PCIDevice {
  539. ret := []string{}
  540. for busId, group := range g.group {
  541. if groupNum == group {
  542. ret = append(ret, busId)
  543. }
  544. }
  545. devs := []*PCIDevice{}
  546. for _, addr := range ret {
  547. if addr == selfAddr {
  548. continue
  549. }
  550. if len(addr) < 5 {
  551. log.Warningf("Invalid addr %q of %q iommu_group[%s], skip it", addr, selfAddr, groupNum)
  552. continue
  553. }
  554. dev, _ := detectPCIDevByAddrWithoutIOMMUGroup(addr[5:])
  555. if dev != nil {
  556. if dev.VendorId == vendorId {
  557. devs = append(devs, dev)
  558. } else {
  559. log.Warningf("Skip append %q iommu_group[%s] device %s", selfAddr, groupNum, dev.String())
  560. }
  561. }
  562. }
  563. return devs
  564. }
  565. func (g *IOMMUGroup) FindSameGroupDevs(devAddr string, vendorId string) []*PCIDevice {
  566. // devAddr: '0000:3f:0f.3' or '3f:0f.3' format
  567. if len(devAddr) == 7 {
  568. devAddr = fmt.Sprintf("0000:%s", devAddr)
  569. }
  570. group, ok := g.group[devAddr]
  571. if !ok {
  572. return nil
  573. }
  574. return g.ListDevices(group, devAddr, vendorId)
  575. }
  576. func (g *IOMMUGroup) String() string {
  577. return jsonutils.Marshal(g.group).PrettyString()
  578. }
  579. func detectPCIDevByAddr(addr string) (*PCIDevice, error) {
  580. return NewPCIDevice(addr)
  581. }
  582. func detectPCIDevByAddrWithoutIOMMUGroup(addr string) (*PCIDevice, error) {
  583. ret, err := bashOutput(fmt.Sprintf("lspci -nnmm -s %s", addr))
  584. if err != nil {
  585. return nil, err
  586. }
  587. line := strings.Join(ret, "")
  588. if line == "" {
  589. return nil, nil
  590. }
  591. return NewPCIDevice2(line), nil
  592. }
  593. func getDeviceCmd(dev IDevice, index int) string {
  594. passthroughCmd := dev.GetPassthroughCmd(index)
  595. groupDevCmd := dev.GetIOMMUGroupDeviceCmd()
  596. if len(groupDevCmd) != 0 {
  597. passthroughCmd = fmt.Sprintf("%s%s", passthroughCmd, groupDevCmd)
  598. }
  599. return passthroughCmd
  600. }