Commit 6ee7ed80 by Zuul Committed by Gerrit Code Review

Merge "FPGA driver support"

parents af5dd8e1 5b724229
# Copyright 2018 Intel, Inc.
#
# 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 os
import glob
from oslo_log import log as logging
__import__('pkg_resources').declare_namespace(__name__)
__import__(".".join([__package__, 'base']))
LOG = logging.getLogger(__name__)
def load_fpga_vendor_driver():
files = glob.glob(os.path.join(os.path.dirname(__file__), "*/driver*"))
modules = set(map(lambda s: ".".join(s.rsplit(".")[0].rsplit("/", 2)[-2:]),
files))
for m in modules:
try:
__import__(".".join([__package__, m]))
LOG.debug("Successfully loaded FPGA vendor driver: %s." % m)
except ImportError as e:
LOG.error("Failed to load FPGA vendor driver: %s. Details: %s"
% (m, e))
load_fpga_vendor_driver()
# Copyright 2018 Intel, Inc.
#
# 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 FPGA driver implementation.
"""
from cyborg.accelerator.drivers.fpga import utils
VENDOR_MAPS = {"0x8086": "intel"}
class FPGADriver(object):
"""Base class for FPGA drivers.
This is just a virtual FPGA drivers interface.
Vedor should implement their specific drivers.
"""
@classmethod
def create(cls, vendor, *args, **kwargs):
for sclass in cls.__subclasses__():
vendor = VENDOR_MAPS.get(vendor, vendor)
if vendor == sclass.VENDOR:
return sclass(*args, **kwargs)
raise LookupError("Not find the FPGA driver for vendor %s" % vendor)
def __init__(self, *args, **kwargs):
pass
def discover(self):
raise NotImplementedError()
def program(self, device_path, image):
raise NotImplementedError()
@classmethod
def discover_vendors(cls):
return utils.discover_vendors()
# Copyright 2018 Intel, Inc.
#
# 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 Intel FPGA driver implementation.
"""
import subprocess
from cyborg.accelerator.drivers.fpga.base import FPGADriver
from cyborg.accelerator.drivers.fpga.intel import sysinfo
class IntelFPGADriver(FPGADriver):
"""Base class for FPGA drivers.
This is just a virtual FPGA drivers interface.
Vedor should implement their specific drivers.
"""
VENDOR = "intel"
def __init__(self, *args, **kwargs):
pass
def discover(self):
return sysinfo.fpga_tree()
def program(self, device_path, image):
bdf = ""
path = sysinfo.find_pf_by_vf(device_path) if sysinfo.is_vf(
device_path) else device_path
if sysinfo.is_bdf(device_path):
bdf = sysinfo.get_pf_bdf(device_path)
else:
bdf = sysinfo.get_bdf_by_path(path)
bdfs = sysinfo.split_bdf(bdf)
cmd = ["sudo", "fpgaconf"]
for i in zip(["-b", "-d", "-f"], bdfs):
cmd.extend(i)
cmd.append(image)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
# FIXME Should log p.communicate(), p.stderr
p.wait()
return p.returncode
# Copyright 2018 Intel, Inc.
#
# 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 Intel FPGA driver implementation.
"""
# from cyborg.accelerator.drivers.fpga.base import FPGADriver
import glob
import os
import re
SYS_FPGA = "/sys/class/fpga"
DEVICE = "device"
PF = "physfn"
VF = "virtfn*"
BDF_PATTERN = re.compile(
"^[a-fA-F\d]{4}:[a-fA-F\d]{2}:[a-fA-F\d]{2}\.[a-fA-F\d]$")
DEVICE_FILE_MAP = {"vendor": "vendor_id",
"device": "product_id",
"sriov_numvfs": "pr_num"}
DEVICE_FILE_HANDLER = {}
DEVICE_EXPOSED = ["vendor", "device", "sriov_numvfs"]
def all_fpgas():
# glob.glob1("/sys/class/fpga", "*")
return glob.glob(os.path.join(SYS_FPGA, "*"))
def all_vf_fpgas():
return [dev.rsplit("/", 2)[0] for dev in
glob.glob(os.path.join(SYS_FPGA, "*/device/physfn"))]
def all_pure_pf_fpgas():
return [dev.rsplit("/", 2)[0] for dev in
glob.glob(os.path.join(SYS_FPGA, "*/device/virtfn0"))]
def target_symbolic_map():
maps = {}
for f in glob.glob(os.path.join(SYS_FPGA, "*/device")):
maps[os.path.realpath(f)] = os.path.dirname(f)
return maps
def bdf_path_map():
maps = {}
for f in glob.glob(os.path.join(SYS_FPGA, "*/device")):
maps[os.path.basename(os.path.realpath(f))] = os.path.dirname(f)
return maps
def all_vfs_in_pf_fpgas(pf_path):
maps = target_symbolic_map()
vfs = glob.glob(os.path.join(pf_path, "device/virtfn*"))
return [maps[os.path.realpath(vf)] for vf in vfs]
def all_pf_fpgas():
return [dev.rsplit("/", 2)[0] for dev in
glob.glob(os.path.join(SYS_FPGA, "*/device/sriov_totalvfs"))]
def is_vf(path):
return True if glob.glob(os.path.join(path, "device/physfn")) else False
def find_pf_by_vf(path):
maps = target_symbolic_map()
p = os.path.realpath(os.path.join(path, "device/physfn"))
return maps[p]
def is_bdf(bdf):
return True if BDF_PATTERN.match(bdf) else False
def get_bdf_by_path(path):
return os.path.basename(os.readlink(os.path.join(path, "device")))
def split_bdf(bdf):
return ["0x" + v for v in bdf.replace(".", ":").rsplit(":")[1:]]
def get_pf_bdf(bdf):
path = bdf_path_map().get(bdf)
if path:
path = find_pf_by_vf(path) if is_vf(path) else path
return get_bdf_by_path(path)
return bdf
def fpga_device(path):
infos = {}
def read_line(filename):
with open(filename) as f:
return f.readline().strip()
# NOTE "In 3.x, os.path.walk is removed in favor of os.walk."
for (dirpath, dirnames, filenames) in os.walk(path):
for filename in filenames:
if filename in DEVICE_EXPOSED:
key = DEVICE_FILE_MAP.get(filename) or filename
if key in DEVICE_FILE_HANDLER and callable(
DEVICE_FILE_HANDLER(key)):
infos[key] = DEVICE_FILE_HANDLER(key)(
os.path.join(dirpath, filename))
else:
infos[key] = read_line(os.path.join(dirpath, filename))
return infos
def fpga_tree():
def gen_fpga_infos(path, vf=True):
name = os.path.basename(path)
dpath = os.path.realpath(os.path.join(path, DEVICE))
bdf = os.path.basename(dpath)
func = "vf" if vf else "pf"
pf_bdf = os.path.basename(
os.path.realpath(os.path.join(dpath, PF))) if vf else ""
fpga = {"path": path, "function": func,
"devices": bdf, "assignable": True,
"parent_devices": pf_bdf,
"name": name}
d_info = fpga_device(dpath)
fpga.update(d_info)
return fpga
devs = []
pure_pfs = all_pure_pf_fpgas()
for pf in all_pf_fpgas():
fpga = gen_fpga_infos(pf, False)
if pf in pure_pfs:
fpga["assignable"] = False
fpga["regions"] = []
vfs = all_vfs_in_pf_fpgas(pf)
for vf in vfs:
vf_fpga = gen_fpga_infos(vf, True)
fpga["regions"].append(vf_fpga)
devs.append(fpga)
return devs
# Copyright 2018 Intel, Inc.
#
# 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.
"""
Utils for FPGA driver.
"""
import glob
import re
VENDORS = ["intel"] # can extend, such as ["intel", "xilinx"]
SYS_FPGA_PATH = "/sys/class/fpga"
VENDORS_PATTERN = re.compile("|".join(["(%s)" % v for v in VENDORS]))
def discover_vendors():
vendors = set()
for p in glob.glob1(SYS_FPGA_PATH, "*"):
m = VENDORS_PATTERN.match(p)
if m:
vendors.add(m.group())
return list(vendors)
# Copyright 2018 Intel, Inc.
#
# 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 mock
import os
import subprocess
import fixtures
from cyborg.accelerator.drivers.fpga.base import FPGADriver
from cyborg.accelerator.drivers.fpga.intel import sysinfo
from cyborg.tests import base
from cyborg.tests.unit.accelerator.drivers.fpga.intel import prepare_test_data
class TestFPGADriver(base.TestCase):
def setUp(self):
super(TestFPGADriver, self).setUp()
self.syspath = sysinfo.SYS_FPGA
sysinfo.SYS_FPGA = "/sys/class/fpga"
tmp_sys_dir = self.useFixture(fixtures.TempDir())
prepare_test_data.create_fake_sysfs(tmp_sys_dir.path)
sysinfo.SYS_FPGA = os.path.join(
tmp_sys_dir.path, sysinfo.SYS_FPGA.split("/", 1)[-1])
def tearDown(self):
super(TestFPGADriver, self).tearDown()
sysinfo.SYS_FPGA = self.syspath
def test_create(self):
FPGADriver.create("intel")
self.assertRaises(LookupError, FPGADriver.create, "xilinx")
def test_discover(self):
d = FPGADriver()
self.assertRaises(NotImplementedError, d.discover)
def test_program(self):
d = FPGADriver()
self.assertRaises(NotImplementedError, d.program, "path", "image")
def test_intel_discover(self):
expect = [{'function': 'pf', 'assignable': False, 'pr_num': '1',
'vendor_id': '0x8086', 'devices': '0000:5e:00.0',
'regions': [{
'function': 'vf', 'assignable': True,
'product_id': '0xbcc1',
'name': 'intel-fpga-dev.2',
'parent_devices': '0000:5e:00.0',
'path': '%s/intel-fpga-dev.2' % sysinfo.SYS_FPGA,
'vendor_id': '0x8086',
'devices': '0000:5e:00.1'}],
'name': 'intel-fpga-dev.0',
'parent_devices': '',
'path': '%s/intel-fpga-dev.0' % sysinfo.SYS_FPGA,
'product_id': '0xbcc0'},
{'function': 'pf', 'assignable': True, 'pr_num': '0',
'vendor_id': '0x8086', 'devices': '0000:be:00.0',
'name': 'intel-fpga-dev.1',
'parent_devices': '',
'path': '%s/intel-fpga-dev.1' % sysinfo.SYS_FPGA,
'product_id': '0xbcc0'}]
expect.sort()
intel = FPGADriver.create("intel")
fpgas = intel.discover()
fpgas.sort()
self.assertEqual(2, len(fpgas))
self.assertEqual(fpgas, expect)
@mock.patch.object(subprocess, 'Popen', autospec=True)
def test_intel_program(self, mock_popen):
class p(object):
returncode = 0
def wait(self):
pass
b = "0x5e"
d = "0x00"
f = "0x0"
expect_cmd = ['sudo', 'fpgaconf', '-b', b,
'-d', d, '-f', f, '/path/image']
mock_popen.return_value = p()
intel = FPGADriver.create("intel")
# program VF
intel.program("0000:5e:00.1", "/path/image")
mock_popen.assert_called_with(expect_cmd, stdout=subprocess.PIPE)
# program PF
intel.program("0000:5e:00.0", "/path/image")
mock_popen.assert_called_with(expect_cmd, stdout=subprocess.PIPE)
# Copyright 2018 Intel, Inc.
#
# 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 mock
import os
import subprocess
import fixtures
from cyborg.accelerator.drivers.fpga.intel import sysinfo
from cyborg.accelerator.drivers.fpga.intel.driver import IntelFPGADriver
from cyborg.tests import base
from cyborg.tests.unit.accelerator.drivers.fpga.intel import prepare_test_data
class TestIntelFPGADriver(base.TestCase):
def setUp(self):
super(TestIntelFPGADriver, self).setUp()
self.syspath = sysinfo.SYS_FPGA
sysinfo.SYS_FPGA = "/sys/class/fpga"
tmp_sys_dir = self.useFixture(fixtures.TempDir())
prepare_test_data.create_fake_sysfs(tmp_sys_dir.path)
sysinfo.SYS_FPGA = os.path.join(
tmp_sys_dir.path, sysinfo.SYS_FPGA.split("/", 1)[-1])
def tearDown(self):
super(TestIntelFPGADriver, self).tearDown()
sysinfo.SYS_FPGA = self.syspath
def test_discover(self):
expect = [{'function': 'pf', 'assignable': False, 'pr_num': '1',
'vendor_id': '0x8086', 'devices': '0000:5e:00.0',
'regions': [{
'function': 'vf', 'assignable': True,
'product_id': '0xbcc1',
'name': 'intel-fpga-dev.2',
'parent_devices': '0000:5e:00.0',
'path': '%s/intel-fpga-dev.2' % sysinfo.SYS_FPGA,
'vendor_id': '0x8086',
'devices': '0000:5e:00.1'}],
'name': 'intel-fpga-dev.0',
'parent_devices': '',
'path': '%s/intel-fpga-dev.0' % sysinfo.SYS_FPGA,
'product_id': '0xbcc0'},
{'function': 'pf', 'assignable': True, 'pr_num': '0',
'vendor_id': '0x8086', 'devices': '0000:be:00.0',
'parent_devices': '',
'name': 'intel-fpga-dev.1',
'path': '%s/intel-fpga-dev.1' % sysinfo.SYS_FPGA,
'product_id': '0xbcc0'}]
expect.sort()
intel = IntelFPGADriver()
fpgas = intel.discover()
fpgas.sort()
self.assertEqual(2, len(fpgas))
self.assertEqual(fpgas, expect)
@mock.patch.object(subprocess, 'Popen', autospec=True)
def test_intel_program(self, mock_popen):
class p(object):
returncode = 0
def wait(self):
pass
b = "0x5e"
d = "0x00"
f = "0x0"
expect_cmd = ['sudo', 'fpgaconf', '-b', b,
'-d', d, '-f', f, '/path/image']
mock_popen.return_value = p()
intel = IntelFPGADriver()
# program VF
intel.program("0000:5e:00.1", "/path/image")
mock_popen.assert_called_with(expect_cmd, stdout=subprocess.PIPE)
# program PF
intel.program("0000:5e:00.0", "/path/image")
mock_popen.assert_called_with(expect_cmd, stdout=subprocess.PIPE)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment