pci unify management
bp pcie-unified-management Change-Id: I2a285b61c4f41e999670715dfa2cef8c910467b9 Change-Id: Iaddcd2d5dfef987b3d11d30a76e2775c8e60ba38
This commit is contained in:
parent
63c239bf3e
commit
5f896d2931
0
cyborg/accelerator/drivers/pci/__init__.py
Normal file
0
cyborg/accelerator/drivers/pci/__init__.py
Normal file
29
cyborg/accelerator/drivers/pci/base.py
Normal file
29
cyborg/accelerator/drivers/pci/base.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2024 Inspur.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cyborg Pci driver implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PciDriver(object):
|
||||||
|
"""Base class for Pci drivers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def discover(self):
|
||||||
|
raise NotImplementedError()
|
290
cyborg/accelerator/drivers/pci/devspec.py
Normal file
290
cyborg/accelerator/drivers/pci/devspec.py
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from cyborg.accelerator.drivers.pci import utils
|
||||||
|
from cyborg.common import exception
|
||||||
|
from cyborg.common.i18n import _
|
||||||
|
|
||||||
|
MAX_VENDOR_ID = 0xFFFF
|
||||||
|
MAX_PRODUCT_ID = 0xFFFF
|
||||||
|
MAX_FUNC = 0x7
|
||||||
|
MAX_DOMAIN = 0xFFFF
|
||||||
|
MAX_BUS = 0xFF
|
||||||
|
MAX_SLOT = 0x1F
|
||||||
|
ANY = '*'
|
||||||
|
REGEX_ANY = '.*'
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class PciAddressSpec(object):
|
||||||
|
"""Abstract class for all PCI address spec styles
|
||||||
|
|
||||||
|
This class checks the address fields of the pci.passthrough_whitelist
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def match(self, pci_addr):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_single_address(self):
|
||||||
|
return all([
|
||||||
|
all(c in string.hexdigits for c in self.domain),
|
||||||
|
all(c in string.hexdigits for c in self.bus),
|
||||||
|
all(c in string.hexdigits for c in self.slot),
|
||||||
|
all(c in string.hexdigits for c in self.func)])
|
||||||
|
|
||||||
|
def _set_pci_dev_info(self, prop, maxval, hex_value):
|
||||||
|
a = getattr(self, prop)
|
||||||
|
if a == ANY:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
v = int(a, 16)
|
||||||
|
except ValueError:
|
||||||
|
raise exception.PciConfigInvalidWhitelist(
|
||||||
|
reason=_("property %(property)s ('%(attr)s') does not parse "
|
||||||
|
"as a hex number.") % {'property': prop, 'attr': a})
|
||||||
|
if v > maxval:
|
||||||
|
raise exception.PciConfigInvalidWhitelist(
|
||||||
|
reason=_("property %(property)s (%(attr)s) is greater than "
|
||||||
|
"the maximum allowable value (%(max)X).") % {
|
||||||
|
'property': prop, 'attr': a, 'max': maxval})
|
||||||
|
setattr(self, prop, hex_value % v)
|
||||||
|
|
||||||
|
|
||||||
|
class PhysicalPciAddress(PciAddressSpec):
|
||||||
|
"""Manages the address fields for a fully-qualified PCI address.
|
||||||
|
|
||||||
|
This function class will validate the address fields for a single
|
||||||
|
PCI device.
|
||||||
|
"""
|
||||||
|
def __init__(self, pci_addr):
|
||||||
|
try:
|
||||||
|
if isinstance(pci_addr, dict):
|
||||||
|
self.domain = pci_addr['domain']
|
||||||
|
self.bus = pci_addr['bus']
|
||||||
|
self.slot = pci_addr['slot']
|
||||||
|
self.func = pci_addr['function']
|
||||||
|
else:
|
||||||
|
self.domain, self.bus, self.slot, self.func = (
|
||||||
|
utils.get_pci_address_fields(pci_addr))
|
||||||
|
self._set_pci_dev_info('func', MAX_FUNC, '%1x')
|
||||||
|
self._set_pci_dev_info('domain', MAX_DOMAIN, '%04x')
|
||||||
|
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
|
||||||
|
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
||||||
|
|
||||||
|
def match(self, phys_pci_addr):
|
||||||
|
conditions = [
|
||||||
|
self.domain == phys_pci_addr.domain,
|
||||||
|
self.bus == phys_pci_addr.bus,
|
||||||
|
self.slot == phys_pci_addr.slot,
|
||||||
|
self.func == phys_pci_addr.func,
|
||||||
|
]
|
||||||
|
return all(conditions)
|
||||||
|
|
||||||
|
|
||||||
|
class PciAddressGlobSpec(PciAddressSpec):
|
||||||
|
"""Manages the address fields with glob style.
|
||||||
|
|
||||||
|
This function class will validate the address fields with glob style,
|
||||||
|
check for wildcards, and insert wildcards where the field is left blank.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pci_addr):
|
||||||
|
self.domain = ANY
|
||||||
|
self.bus = ANY
|
||||||
|
self.slot = ANY
|
||||||
|
self.func = ANY
|
||||||
|
|
||||||
|
dbs, sep, func = pci_addr.partition('.')
|
||||||
|
if func:
|
||||||
|
self.func = func.strip()
|
||||||
|
self._set_pci_dev_info('func', MAX_FUNC, '%01x')
|
||||||
|
if dbs:
|
||||||
|
dbs_fields = dbs.split(':')
|
||||||
|
if len(dbs_fields) > 3:
|
||||||
|
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
||||||
|
# If we got a partial address like ":00.", we need to turn this
|
||||||
|
# into a domain of ANY, a bus of ANY, and a slot of 00. This code
|
||||||
|
# allows the address bus and/or domain to be left off
|
||||||
|
dbs_all = [ANY] * (3 - len(dbs_fields))
|
||||||
|
dbs_all.extend(dbs_fields)
|
||||||
|
dbs_checked = [s.strip() or ANY for s in dbs_all]
|
||||||
|
self.domain, self.bus, self.slot = dbs_checked
|
||||||
|
self._set_pci_dev_info('domain', MAX_DOMAIN, '%04x')
|
||||||
|
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
|
||||||
|
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
|
||||||
|
|
||||||
|
def match(self, phys_pci_addr):
|
||||||
|
conditions = [
|
||||||
|
self.domain in (ANY, phys_pci_addr.domain),
|
||||||
|
self.bus in (ANY, phys_pci_addr.bus),
|
||||||
|
self.slot in (ANY, phys_pci_addr.slot),
|
||||||
|
self.func in (ANY, phys_pci_addr.func)
|
||||||
|
]
|
||||||
|
return all(conditions)
|
||||||
|
|
||||||
|
|
||||||
|
class PciAddressRegexSpec(PciAddressSpec):
|
||||||
|
"""Manages the address fields with regex style.
|
||||||
|
|
||||||
|
This function class will validate the address fields with regex style.
|
||||||
|
The validation includes check for all PCI address attributes and validate
|
||||||
|
their regex.
|
||||||
|
"""
|
||||||
|
def __init__(self, pci_addr):
|
||||||
|
try:
|
||||||
|
self.domain = pci_addr.get('domain', REGEX_ANY)
|
||||||
|
self.bus = pci_addr.get('bus', REGEX_ANY)
|
||||||
|
self.slot = pci_addr.get('slot', REGEX_ANY)
|
||||||
|
self.func = pci_addr.get('function', REGEX_ANY)
|
||||||
|
self.domain_regex = re.compile(self.domain)
|
||||||
|
self.bus_regex = re.compile(self.bus)
|
||||||
|
self.slot_regex = re.compile(self.slot)
|
||||||
|
self.func_regex = re.compile(self.func)
|
||||||
|
except re.error:
|
||||||
|
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
||||||
|
|
||||||
|
def match(self, phys_pci_addr):
|
||||||
|
conditions = [
|
||||||
|
bool(self.domain_regex.match(phys_pci_addr.domain)),
|
||||||
|
bool(self.bus_regex.match(phys_pci_addr.bus)),
|
||||||
|
bool(self.slot_regex.match(phys_pci_addr.slot)),
|
||||||
|
bool(self.func_regex.match(phys_pci_addr.func))
|
||||||
|
]
|
||||||
|
return all(conditions)
|
||||||
|
|
||||||
|
|
||||||
|
class WhitelistPciAddress(object):
|
||||||
|
"""Manages the address fields of the whitelist.
|
||||||
|
|
||||||
|
This class checks the address fields of the pci.passthrough_whitelist
|
||||||
|
configuration option, validating the address fields.
|
||||||
|
Example configs:
|
||||||
|
|
||||||
|
| [pci]
|
||||||
|
| passthrough_whitelist = {"address":"*:0a:00.*",
|
||||||
|
| "physical_network":"physnet1"}
|
||||||
|
| passthrough_whitelist = {"address": {"domain": ".*",
|
||||||
|
"bus": "02",
|
||||||
|
"slot": "01",
|
||||||
|
"function": "[0-2]"},
|
||||||
|
"physical_network":"net1"}
|
||||||
|
| passthrough_whitelist = {"vendor_id":"1137","product_id":"0071"}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, pci_addr, is_physical_function):
|
||||||
|
self.is_physical_function = is_physical_function
|
||||||
|
self._init_address_fields(pci_addr)
|
||||||
|
|
||||||
|
def _check_physical_function(self):
|
||||||
|
if self.pci_address_spec.is_single_address():
|
||||||
|
self.is_physical_function = (
|
||||||
|
utils.is_physical_function(
|
||||||
|
self.pci_address_spec.domain,
|
||||||
|
self.pci_address_spec.bus,
|
||||||
|
self.pci_address_spec.slot,
|
||||||
|
self.pci_address_spec.func))
|
||||||
|
|
||||||
|
def _init_address_fields(self, pci_addr):
|
||||||
|
if not self.is_physical_function:
|
||||||
|
if isinstance(pci_addr, six.string_types):
|
||||||
|
self.pci_address_spec = PciAddressGlobSpec(pci_addr)
|
||||||
|
elif isinstance(pci_addr, dict):
|
||||||
|
self.pci_address_spec = PciAddressRegexSpec(pci_addr)
|
||||||
|
else:
|
||||||
|
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
||||||
|
self._check_physical_function()
|
||||||
|
else:
|
||||||
|
self.pci_address_spec = PhysicalPciAddress(pci_addr)
|
||||||
|
|
||||||
|
def match(self, pci_addr, pci_phys_addr):
|
||||||
|
"""Match a device to this PciAddress. Assume this is called given
|
||||||
|
pci_addr and pci_phys_addr reported by libvirt, no attempt is made to
|
||||||
|
verify if pci_addr is a VF of pci_phys_addr.
|
||||||
|
|
||||||
|
:param pci_addr: PCI address of the device to match.
|
||||||
|
:param pci_phys_addr: PCI address of the parent of the device to match
|
||||||
|
(or None if the device is not a VF).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Try to match on the parent PCI address if the PciDeviceSpec is a
|
||||||
|
# PF (sriov is available) and the device to match is a VF. This
|
||||||
|
# makes it possible to specify the PCI address of a PF in the
|
||||||
|
# pci.passthrough_whitelist to match any of its VFs' PCI addresses.
|
||||||
|
if self.is_physical_function and pci_phys_addr:
|
||||||
|
pci_phys_addr_obj = PhysicalPciAddress(pci_phys_addr)
|
||||||
|
if self.pci_address_spec.match(pci_phys_addr_obj):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Try to match on the device PCI address only.
|
||||||
|
pci_addr_obj = PhysicalPciAddress(pci_addr)
|
||||||
|
return self.pci_address_spec.match(pci_addr_obj)
|
||||||
|
|
||||||
|
|
||||||
|
class PciDeviceSpec(PciAddressSpec):
|
||||||
|
def __init__(self, dev_spec):
|
||||||
|
self.tags = dev_spec
|
||||||
|
self._init_dev_details()
|
||||||
|
|
||||||
|
def _init_dev_details(self):
|
||||||
|
self.vendor_id = self.tags.pop("vendor_id", ANY)
|
||||||
|
self.product_id = self.tags.pop("product_id", ANY)
|
||||||
|
# Note(moshele): The address attribute can be a string or a dict.
|
||||||
|
# For glob syntax or specific pci it is a string and for regex syntax
|
||||||
|
# it is a dict. The WhitelistPciAddress class handles both types.
|
||||||
|
self.address = self.tags.pop("address", None)
|
||||||
|
self.dev_name = self.tags.pop("devname", None)
|
||||||
|
|
||||||
|
self.vendor_id = self.vendor_id.strip()
|
||||||
|
self._set_pci_dev_info('vendor_id', MAX_VENDOR_ID, '%04x')
|
||||||
|
self._set_pci_dev_info('product_id', MAX_PRODUCT_ID, '%04x')
|
||||||
|
|
||||||
|
if self.address and self.dev_name:
|
||||||
|
raise exception.PciDeviceInvalidDeviceName()
|
||||||
|
if not self.dev_name:
|
||||||
|
pci_address = self.address or "*:*:*.*"
|
||||||
|
self.address = WhitelistPciAddress(pci_address, False)
|
||||||
|
|
||||||
|
def match(self, dev_dict):
|
||||||
|
if self.dev_name:
|
||||||
|
address_str, pf = utils.get_function_by_ifname(
|
||||||
|
self.dev_name)
|
||||||
|
if not address_str:
|
||||||
|
return False
|
||||||
|
# Note(moshele): In this case we always passing a string
|
||||||
|
# of the PF pci address
|
||||||
|
address_obj = WhitelistPciAddress(address_str, pf)
|
||||||
|
elif self.address:
|
||||||
|
address_obj = self.address
|
||||||
|
return all([
|
||||||
|
self.vendor_id in (ANY, dev_dict['vendor_id']),
|
||||||
|
self.product_id in (ANY, dev_dict['product_id']),
|
||||||
|
address_obj.match(dev_dict['address'],
|
||||||
|
dev_dict.get('parent_addr'))])
|
||||||
|
|
||||||
|
def match_pci_obj(self, pci_obj):
|
||||||
|
return self.match({'vendor_id': pci_obj.vendor_id,
|
||||||
|
'product_id': pci_obj.product_id,
|
||||||
|
'address': pci_obj.address,
|
||||||
|
'parent_addr': pci_obj.parent_addr})
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
return self.tags
|
0
cyborg/accelerator/drivers/pci/pci/__init__.py
Normal file
0
cyborg/accelerator/drivers/pci/pci/__init__.py
Normal file
30
cyborg/accelerator/drivers/pci/pci/driver.py
Normal file
30
cyborg/accelerator/drivers/pci/pci/driver.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright 2024 Inspur.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cyborg PCI driver implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from cyborg.accelerator.drivers.pci.base import PciDriver
|
||||||
|
from cyborg.accelerator.drivers.pci.pci import sysinfo
|
||||||
|
|
||||||
|
|
||||||
|
class PCIDriver(PciDriver):
|
||||||
|
"""Class for Pci drivers.
|
||||||
|
Vendor should implement their specific drivers in this class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def discover(self):
|
||||||
|
return sysinfo.discover()
|
147
cyborg/accelerator/drivers/pci/pci/sysinfo.py
Normal file
147
cyborg/accelerator/drivers/pci/pci/sysinfo.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# Copyright 2024 Inspur.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Cyborg PCI driver implementation.
|
||||||
|
"""
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from cyborg.accelerator.common import utils
|
||||||
|
from cyborg.accelerator.drivers.pci import utils as pci_utils
|
||||||
|
from cyborg.accelerator.drivers.pci import whitelist
|
||||||
|
from cyborg.common import constants
|
||||||
|
import cyborg.conf
|
||||||
|
from cyborg.objects.driver_objects import driver_attach_handle
|
||||||
|
from cyborg.objects.driver_objects import driver_attribute
|
||||||
|
from cyborg.objects.driver_objects import driver_controlpath_id
|
||||||
|
from cyborg.objects.driver_objects import driver_deployable
|
||||||
|
from cyborg.objects.driver_objects import driver_device
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cyborg.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
def _get_traits(vendor_id, product_id):
|
||||||
|
"""Generate traits for PCIs.
|
||||||
|
: param vendor_id: vendor_id of PCI, eg."10de"
|
||||||
|
: param product_id: product_id of PCI, eg."1eb8".
|
||||||
|
|
||||||
|
Example PGPU traits:
|
||||||
|
{traits:["OWNER_CYBORG", "CUSTOM_PCI_1EB8"]}
|
||||||
|
"""
|
||||||
|
vendor_name = pci_utils.VENDOR_MAPS.get(vendor_id).upper()
|
||||||
|
traits = ["CUSTOM_PCI_" + vendor_name]
|
||||||
|
# PCIE trait
|
||||||
|
product_trait = "_".join(('CUSTOM_PCI_PRODUCT_ID', product_id.upper()))
|
||||||
|
traits.append(product_trait)
|
||||||
|
return {"traits": traits}
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_attribute_list(pci):
|
||||||
|
attr_list = []
|
||||||
|
index = 0
|
||||||
|
for k, v in pci.items():
|
||||||
|
if k == "rc":
|
||||||
|
driver_attr = driver_attribute.DriverAttribute()
|
||||||
|
driver_attr.key, driver_attr.value = k, v
|
||||||
|
attr_list.append(driver_attr)
|
||||||
|
if k == "traits":
|
||||||
|
values = pci.get(k, [])
|
||||||
|
for val in values:
|
||||||
|
driver_attr = driver_attribute.DriverAttribute(
|
||||||
|
key="trait" + str(index), value=val)
|
||||||
|
index = index + 1
|
||||||
|
attr_list.append(driver_attr)
|
||||||
|
return attr_list
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_attach_handle(pci):
|
||||||
|
driver_ah = driver_attach_handle.DriverAttachHandle()
|
||||||
|
driver_ah.in_use = False
|
||||||
|
driver_ah.attach_type = constants.AH_TYPE_PCI
|
||||||
|
driver_ah.attach_info = utils.pci_str_to_json(pci["devices"])
|
||||||
|
return driver_ah
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_dep_list(pci):
|
||||||
|
dep_list = []
|
||||||
|
driver_dep = driver_deployable.DriverDeployable()
|
||||||
|
driver_dep.attribute_list = _generate_attribute_list(pci)
|
||||||
|
driver_dep.attach_handle_list = []
|
||||||
|
# NOTE(wangzhh): The name of deployable should be unique, its format is
|
||||||
|
# under disscussion, may looks like
|
||||||
|
# <ComputeNodeName>_<NumaNodeName>_<CyborgName>_<NumInHost>
|
||||||
|
# NOTE(yumeng) Since Wallaby release, the deplpyable_name is named as
|
||||||
|
# <Compute_hostname>_<Device_address>
|
||||||
|
driver_dep.name = pci.get('hostname', '') + '_' + pci["devices"]
|
||||||
|
driver_dep.driver_name = \
|
||||||
|
pci_utils.VENDOR_MAPS.get(pci["vendor_id"]).upper()
|
||||||
|
driver_dep.num_accelerators = 1
|
||||||
|
driver_dep.attach_handle_list = [_generate_attach_handle(pci)]
|
||||||
|
dep_list.append(driver_dep)
|
||||||
|
return dep_list, driver_dep.num_accelerators
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_controlpath_id(pci):
|
||||||
|
driver_cpid = driver_controlpath_id.DriverControlPathID()
|
||||||
|
driver_cpid.cpid_type = "PCI"
|
||||||
|
driver_cpid.cpid_info = utils.pci_str_to_json(pci["devices"])
|
||||||
|
return driver_cpid
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_driver_device(pci):
|
||||||
|
driver_device_obj = driver_device.DriverDevice()
|
||||||
|
driver_device_obj.vendor = pci['vendor_id']
|
||||||
|
driver_device_obj.model = pci['product_id']
|
||||||
|
std_board_info = {'product_id': pci.get('product_id'),
|
||||||
|
'controller': pci.get('controller'),
|
||||||
|
}
|
||||||
|
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
|
||||||
|
driver_device_obj.type = constants.DEVICE_GPU
|
||||||
|
driver_device_obj.stub = pci.get('stub', False)
|
||||||
|
driver_device_obj.controlpath_id = _generate_controlpath_id(pci)
|
||||||
|
driver_device_obj.deployable_list, ais = _generate_dep_list(pci)
|
||||||
|
driver_device_obj.vendor_board_info = pci.get('vendor_board_info',
|
||||||
|
"miss_vb_info")
|
||||||
|
return driver_device_obj
|
||||||
|
|
||||||
|
|
||||||
|
def _discover_pcis():
|
||||||
|
cyborg.conf.devices.register_dynamic_opts(CONF)
|
||||||
|
# discover pci devices by "lspci"
|
||||||
|
pci_list = []
|
||||||
|
pcis = pci_utils.get_pci_devices()
|
||||||
|
LOG.info('pcis:%s', pcis)
|
||||||
|
# report trait,rc and generate driver object
|
||||||
|
dev_filter = whitelist.Whitelist(CONF.pci.passthrough_whitelist)
|
||||||
|
for pci in pcis:
|
||||||
|
m = dev_filter.device_assignable(pci)
|
||||||
|
if m:
|
||||||
|
pci_dict = m.groupdict()
|
||||||
|
# get hostname for deployable_name usage
|
||||||
|
pci_dict['hostname'] = CONF.host
|
||||||
|
pci_dict["rc"] = constants.RESOURCES["PCI"]
|
||||||
|
traits = _get_traits(pci_dict["vendor_id"],
|
||||||
|
pci_dict["product_id"])
|
||||||
|
pci_dict.update(traits)
|
||||||
|
pci_list.append(_generate_driver_device(pci_dict))
|
||||||
|
LOG.info('pci_list:%s', pci_list)
|
||||||
|
return pci_list
|
||||||
|
|
||||||
|
|
||||||
|
def discover():
|
||||||
|
devs = _discover_pcis()
|
||||||
|
return devs
|
232
cyborg/accelerator/drivers/pci/utils.py
Normal file
232
cyborg/accelerator/drivers/pci/utils.py
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
# Copyright 2024 Inspur.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import six
|
||||||
|
|
||||||
|
from cyborg.common import exception
|
||||||
|
import cyborg.privsep
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PCI_VENDOR_PATTERN = "^(hex{4})$".replace("hex", r"[\da-fA-F]")
|
||||||
|
_PCI_ADDRESS_PATTERN = ("^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".
|
||||||
|
replace("hex", r"[\da-fA-F]").
|
||||||
|
replace("oct", "[0-7]"))
|
||||||
|
_PCI_ADDRESS_REGEX = re.compile(_PCI_ADDRESS_PATTERN)
|
||||||
|
|
||||||
|
_SRIOV_TOTALVFS = "sriov_totalvfs"
|
||||||
|
|
||||||
|
VENDOR_MAPS = {"10de": "nvidia", "102b": "matrox"}
|
||||||
|
|
||||||
|
|
||||||
|
@cyborg.privsep.sys_admin_pctxt.entrypoint
|
||||||
|
def get_pci_devices():
|
||||||
|
cmd = ['lspci', '-nnn', '-D']
|
||||||
|
return processutils.execute(*cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def pci_device_prop_match(pci_dev, specs):
|
||||||
|
"""Check if the pci_dev meet spec requirement
|
||||||
|
|
||||||
|
Specs is a list of PCI device property requirements.
|
||||||
|
An example of device requirement that the PCI should be either:
|
||||||
|
a) Device with vendor_id as 0x8086 and product_id as 0x8259, or
|
||||||
|
b) Device with vendor_id as 0x10de and product_id as 0x10d8:
|
||||||
|
|
||||||
|
[{"vendor_id":"8086", "product_id":"8259"},
|
||||||
|
{"vendor_id":"10de", "product_id":"10d8",
|
||||||
|
"capabilities_network": ["rx", "tx", "tso", "gso"]}]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def _matching_devices(spec):
|
||||||
|
for k, v in spec.items():
|
||||||
|
pci_dev_v = pci_dev.get(k)
|
||||||
|
if isinstance(v, list) and isinstance(pci_dev_v, list):
|
||||||
|
if not all(x in pci_dev.get(k) for x in v):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# We don't need to check case for tags in order to avoid any
|
||||||
|
# mismatch with the tags provided by users for port
|
||||||
|
# binding profile and the ones configured by operators
|
||||||
|
# with pci whitelist option.
|
||||||
|
if isinstance(v, six.string_types):
|
||||||
|
v = v.lower()
|
||||||
|
if isinstance(pci_dev_v, six.string_types):
|
||||||
|
pci_dev_v = pci_dev_v.lower()
|
||||||
|
if pci_dev_v != v:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
return any(_matching_devices(spec) for spec in specs)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_address(address):
|
||||||
|
"""Returns (domain, bus, slot, function) from PCI address that is stored in
|
||||||
|
PciDevice DB table.
|
||||||
|
"""
|
||||||
|
m = _PCI_ADDRESS_REGEX.match(address)
|
||||||
|
if not m:
|
||||||
|
raise exception.PciDeviceWrongAddressFormat(address=address)
|
||||||
|
return m.groups()
|
||||||
|
|
||||||
|
|
||||||
|
def get_pci_address_fields(pci_addr):
|
||||||
|
"""Parse a fully-specified PCI device address.
|
||||||
|
|
||||||
|
Does not validate that the components are valid hex or wildcard values.
|
||||||
|
|
||||||
|
:param pci_addr: A string of the form "<domain>:<bus>:<slot>.<function>".
|
||||||
|
:return: A 4-tuple of strings ("<domain>", "<bus>", "<slot>", "<function>")
|
||||||
|
"""
|
||||||
|
dbs, sep, func = pci_addr.partition('.')
|
||||||
|
domain, bus, slot = dbs.split(':')
|
||||||
|
return domain, bus, slot, func
|
||||||
|
|
||||||
|
|
||||||
|
def get_pci_address(domain, bus, slot, func):
|
||||||
|
"""Assembles PCI address components into a fully-specified PCI address.
|
||||||
|
|
||||||
|
Does not validate that the components are valid hex or wildcard values.
|
||||||
|
|
||||||
|
:param domain, bus, slot, func: Hex or wildcard strings.
|
||||||
|
:return: A string of the form "<domain>:<bus>:<slot>.<function>".
|
||||||
|
"""
|
||||||
|
return '%s:%s:%s.%s' % (domain, bus, slot, func)
|
||||||
|
|
||||||
|
|
||||||
|
def get_function_by_ifname(ifname):
|
||||||
|
"""Given the device name, returns the PCI address of a device
|
||||||
|
and returns True if the address is in a physical function.
|
||||||
|
"""
|
||||||
|
dev_path = "/sys/class/net/%s/device" % ifname
|
||||||
|
if os.path.isdir(dev_path):
|
||||||
|
try:
|
||||||
|
# sriov_totalvfs contains the maximum possible VFs for this PF
|
||||||
|
with open(os.path.join(dev_path, _SRIOV_TOTALVFS)) as fd:
|
||||||
|
sriov_totalvfs = int(fd.read())
|
||||||
|
return (os.readlink(dev_path).strip("./"),
|
||||||
|
sriov_totalvfs > 0)
|
||||||
|
except (IOError, ValueError):
|
||||||
|
return os.readlink(dev_path).strip("./"), False
|
||||||
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
|
def is_physical_function(domain, bus, slot, function):
|
||||||
|
dev_path = "/sys/bus/pci/devices/%(d)s:%(b)s:%(s)s.%(f)s/" % {
|
||||||
|
"d": domain, "b": bus, "s": slot, "f": function}
|
||||||
|
if os.path.isdir(dev_path):
|
||||||
|
try:
|
||||||
|
with open(dev_path + _SRIOV_TOTALVFS) as fd:
|
||||||
|
sriov_totalvfs = int(fd.read())
|
||||||
|
return sriov_totalvfs > 0
|
||||||
|
except (IOError, ValueError):
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sysfs_netdev_path(pci_addr, pf_interface):
|
||||||
|
"""Get the sysfs path based on the PCI address of the device.
|
||||||
|
|
||||||
|
Assumes a networking device - will not check for the existence of the path.
|
||||||
|
"""
|
||||||
|
if pf_interface:
|
||||||
|
return "/sys/bus/pci/devices/%s/physfn/net" % pci_addr
|
||||||
|
return "/sys/bus/pci/devices/%s/net" % pci_addr
|
||||||
|
|
||||||
|
|
||||||
|
def get_ifname_by_pci_address(pci_addr, pf_interface=False):
|
||||||
|
"""Get the interface name based on a VF's pci address.
|
||||||
|
|
||||||
|
The returned interface name is either the parent PF's or that of the VF
|
||||||
|
itself based on the argument of pf_interface.
|
||||||
|
"""
|
||||||
|
dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
|
||||||
|
try:
|
||||||
|
dev_info = os.listdir(dev_path)
|
||||||
|
return dev_info.pop()
|
||||||
|
except Exception:
|
||||||
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_mac_by_pci_address(pci_addr, pf_interface=False):
|
||||||
|
"""Get the MAC address of the nic based on its PCI address.
|
||||||
|
|
||||||
|
Raises PciDeviceNotFoundById in case the pci device is not a NIC
|
||||||
|
"""
|
||||||
|
dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
|
||||||
|
if_name = get_ifname_by_pci_address(pci_addr, pf_interface)
|
||||||
|
addr_file = os.path.join(dev_path, if_name, 'address')
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(addr_file) as f:
|
||||||
|
mac = next(f).strip()
|
||||||
|
return mac
|
||||||
|
except (IOError, StopIteration) as e:
|
||||||
|
LOG.warning("Could not find the expected sysfs file for "
|
||||||
|
"determining the MAC address of the PCI device "
|
||||||
|
"%(addr)s. May not be a NIC. Error: %(e)s",
|
||||||
|
{'addr': pci_addr, 'e': e})
|
||||||
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vf_num_by_pci_address(pci_addr):
|
||||||
|
"""Get the VF number based on a VF's pci address
|
||||||
|
|
||||||
|
A VF is associated with an VF number, which ip link command uses to
|
||||||
|
configure it. This number can be obtained from the PCI device filesystem.
|
||||||
|
"""
|
||||||
|
VIRTFN_RE = re.compile(r"virtfn(\d+)")
|
||||||
|
virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
|
||||||
|
vf_num = None
|
||||||
|
try:
|
||||||
|
for vf_path in glob.iglob(virtfns_path):
|
||||||
|
if re.search(pci_addr, os.readlink(vf_path)):
|
||||||
|
t = VIRTFN_RE.search(vf_path)
|
||||||
|
vf_num = t.group(1)
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if vf_num is None:
|
||||||
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
|
return vf_num
|
||||||
|
|
||||||
|
|
||||||
|
def get_net_name_by_vf_pci_address(vfaddress):
|
||||||
|
"""Given the VF PCI address, returns the net device name.
|
||||||
|
|
||||||
|
Every VF is associated to a PCI network device. This function
|
||||||
|
returns the libvirt name given to this network device; e.g.:
|
||||||
|
|
||||||
|
<device>
|
||||||
|
<name>net_enp8s0f0_90_e2_ba_5e_a6_40</name>
|
||||||
|
...
|
||||||
|
|
||||||
|
In the libvirt parser information tree, the network device stores the
|
||||||
|
network capabilities associated to this device.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mac = get_mac_by_pci_address(vfaddress).split(':')
|
||||||
|
ifname = get_ifname_by_pci_address(vfaddress)
|
||||||
|
return ("net_%(ifname)s_%(mac)s" %
|
||||||
|
{'ifname': ifname, 'mac': '_'.join(mac)})
|
||||||
|
except Exception:
|
||||||
|
LOG.warning("No net device was found for VF %(vfaddress)s",
|
||||||
|
{'vfaddress': vfaddress})
|
||||||
|
return
|
91
cyborg/accelerator/drivers/pci/whitelist.py
Normal file
91
cyborg/accelerator/drivers/pci/whitelist.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# Copyright 2024 Inspur.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from cyborg.accelerator.drivers.pci import devspec
|
||||||
|
from cyborg.common import exception
|
||||||
|
from cyborg.common.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class Whitelist(object):
|
||||||
|
"""White list class to represent assignable pci devices.
|
||||||
|
|
||||||
|
Not all devices on a compute node can be assigned to a guest. The cloud
|
||||||
|
administrator decides which devices can be assigned based on ``vendor_id``
|
||||||
|
or ``product_id``, etc. If no white list is specified, no devices will be
|
||||||
|
assignable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, whitelist_spec=None):
|
||||||
|
"""White list constructor
|
||||||
|
|
||||||
|
For example, the following json string specifies that devices whose
|
||||||
|
vendor_id is '8086' and product_id is '1520' can be assigned
|
||||||
|
to guests. ::
|
||||||
|
|
||||||
|
'[{"product_id":"1520", "vendor_id":"8086"}]'
|
||||||
|
|
||||||
|
:param whitelist_spec: A JSON string for a dictionary or list thereof.
|
||||||
|
Each dictionary specifies the pci device properties requirement.
|
||||||
|
See the definition of ``passthrough_whitelist`` in
|
||||||
|
``nova.conf.pci`` for details and examples.
|
||||||
|
"""
|
||||||
|
if whitelist_spec:
|
||||||
|
self.specs = self._parse_white_list_from_config(whitelist_spec)
|
||||||
|
else:
|
||||||
|
self.specs = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_white_list_from_config(whitelists):
|
||||||
|
"""Parse and validate the pci whitelist from the nova config."""
|
||||||
|
specs = []
|
||||||
|
for jsonspec in whitelists:
|
||||||
|
try:
|
||||||
|
dev_spec = jsonutils.loads(jsonspec)
|
||||||
|
except ValueError:
|
||||||
|
raise exception.PciConfigInvalidWhitelist(
|
||||||
|
reason=_("Invalid entry: '%s'") % jsonspec)
|
||||||
|
if isinstance(dev_spec, dict):
|
||||||
|
dev_spec = [dev_spec]
|
||||||
|
elif not isinstance(dev_spec, list):
|
||||||
|
raise exception.PciConfigInvalidWhitelist(
|
||||||
|
reason=_("Invalid entry: '%s'; "
|
||||||
|
"Expecting list or dict") % jsonspec)
|
||||||
|
|
||||||
|
for ds in dev_spec:
|
||||||
|
if not isinstance(ds, dict):
|
||||||
|
raise exception.PciConfigInvalidWhitelist(
|
||||||
|
reason=_("Invalid entry: '%s'; "
|
||||||
|
"Expecting dict") % ds)
|
||||||
|
|
||||||
|
spec = devspec.PciDeviceSpec(ds)
|
||||||
|
specs.append(spec)
|
||||||
|
|
||||||
|
return specs
|
||||||
|
|
||||||
|
def device_assignable(self, dev):
|
||||||
|
"""Check if a device can be assigned to a guest.
|
||||||
|
|
||||||
|
:param dev: A dictionary describing the device properties
|
||||||
|
"""
|
||||||
|
for spec in self.specs:
|
||||||
|
if spec.match(dev):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_devspec(self, pci_dev):
|
||||||
|
for spec in self.specs:
|
||||||
|
if spec.match_pci_obj(pci_dev):
|
||||||
|
return spec
|
@ -88,6 +88,7 @@ RESOURCES = {
|
|||||||
"QAT": "CUSTOM_QAT",
|
"QAT": "CUSTOM_QAT",
|
||||||
"NIC": "CUSTOM_NIC",
|
"NIC": "CUSTOM_NIC",
|
||||||
"SSD": 'CUSTOM_SSD',
|
"SSD": 'CUSTOM_SSD',
|
||||||
|
"PCI": 'CUSTOM_PCI',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -425,3 +425,12 @@ class FPGAProgramError(CyborgException):
|
|||||||
|
|
||||||
class PciDeviceNotFoundById(NotFound):
|
class PciDeviceNotFoundById(NotFound):
|
||||||
_msg_fmt = _("PCI device %(id)s not found")
|
_msg_fmt = _("PCI device %(id)s not found")
|
||||||
|
|
||||||
|
|
||||||
|
class PciConfigInvalidWhitelist(Invalid):
|
||||||
|
_msg_fmt = _("Invalid PCI devices whitelist config: %(reason)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class PciDeviceInvalidDeviceName(CyborgException):
|
||||||
|
_msg_fmt = _("Invalid PCI whitelist: The PCI whitelist can specify "
|
||||||
|
"devname or address, but not both.")
|
||||||
|
@ -14,6 +14,15 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
pci_group = cfg.OptGroup(
|
||||||
|
name='pci',
|
||||||
|
title='PCI passthrough options')
|
||||||
|
|
||||||
|
pci_opts = [
|
||||||
|
cfg.MultiStrOpt('passthrough_whitelist',
|
||||||
|
default=[],
|
||||||
|
help=" ")
|
||||||
|
]
|
||||||
|
|
||||||
nic_group = cfg.OptGroup(
|
nic_group = cfg.OptGroup(
|
||||||
name='nic_devices',
|
name='nic_devices',
|
||||||
@ -72,6 +81,8 @@ def register_opts(conf):
|
|||||||
conf.register_opts(nic_opts, group=nic_group)
|
conf.register_opts(nic_opts, group=nic_group)
|
||||||
conf.register_group(gpu_group)
|
conf.register_group(gpu_group)
|
||||||
conf.register_opts(vgpu_opts, group=gpu_group)
|
conf.register_opts(vgpu_opts, group=gpu_group)
|
||||||
|
conf.register_group(pci_group)
|
||||||
|
conf.register_opts(pci_opts, group=pci_group)
|
||||||
|
|
||||||
|
|
||||||
def register_dynamic_opts(conf):
|
def register_dynamic_opts(conf):
|
||||||
|
@ -57,6 +57,7 @@ cyborg.accelerator.driver =
|
|||||||
intel_qat_driver = cyborg.accelerator.drivers.qat.intel.driver:IntelQATDriver
|
intel_qat_driver = cyborg.accelerator.drivers.qat.intel.driver:IntelQATDriver
|
||||||
intel_nic_driver = cyborg.accelerator.drivers.nic.intel.driver:IntelNICDriver
|
intel_nic_driver = cyborg.accelerator.drivers.nic.intel.driver:IntelNICDriver
|
||||||
inspur_nvme_ssd_driver = cyborg.accelerator.drivers.ssd.inspur.driver:InspurNVMeSSDDriver
|
inspur_nvme_ssd_driver = cyborg.accelerator.drivers.ssd.inspur.driver:InspurNVMeSSDDriver
|
||||||
|
pci_driver = cyborg.accelerator.drivers.pci.pci.driver:PCIDriver
|
||||||
|
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
cyborg = cyborg.conf.opts:list_opts
|
cyborg = cyborg.conf.opts:list_opts
|
||||||
|
Loading…
Reference in New Issue
Block a user