XenAPI: Add Physical PCI Device (PPCI) Support

Signed-off-by: Yosuke Iwamatsu <y-iwamatsu@ab.jp.nec.com>

diff -r 19970181d6a4 -r fdf409891cd6 tools/python/xen/util/pci.py
--- a/tools/python/xen/util/pci.py	Tue Jul 01 14:50:35 2008 +0100
+++ b/tools/python/xen/util/pci.py	Wed Jul 02 17:59:30 2008 +0900
@@ -8,6 +8,8 @@ import sys
 import sys
 import os, os.path
 import resource
+import re
+import types
 
 PROC_MNT_PATH = '/proc/mounts'
 PROC_PCI_PATH = '/proc/bus/pci/devices'
@@ -22,6 +24,9 @@ SYSFS_PCI_DEV_DEVICE_PATH = '/device'
 SYSFS_PCI_DEV_DEVICE_PATH = '/device'
 SYSFS_PCI_DEV_SUBVENDOR_PATH = '/subsystem_vendor'
 SYSFS_PCI_DEV_SUBDEVICE_PATH = '/subsystem_device'
+SYSFS_PCI_DEV_CLASS_PATH = '/class'
+
+LSPCI_CMD = 'lspci'
 
 PCI_BAR_IO = 0x01
 PCI_BAR_IO_MASK = ~0x03
@@ -32,6 +37,9 @@ MSIX_BIR_MASK = 0x7
 MSIX_BIR_MASK = 0x7
 MSIX_SIZE_MASK = 0x7ff
 
+# Global variable to store information from lspci
+lspci_info = None
+
 #Calculate PAGE_SHIFT: number of bits to shift an address to get the page number
 PAGE_SIZE = resource.getpagesize()
 PAGE_SHIFT = 0
@@ -45,6 +53,15 @@ def PCI_DEVFN(slot, func):
 def PCI_DEVFN(slot, func):
     return ((((slot) & 0x1f) << 3) | ((func) & 0x07))
 
+def parse_hex(val):
+    try:
+        if isinstance(val, types.StringTypes):
+            return int(val, 16)
+        else:
+            return val
+    except ValueError:
+        return None
+
 def find_sysfs_mnt():
     mounts_file = open(PROC_MNT_PATH,'r')
 
@@ -57,6 +74,61 @@ def find_sysfs_mnt():
             return sline[1]
 
     return None
+
+def get_all_pci_names():
+    try:
+        sysfs_mnt = find_sysfs_mnt()
+    except IOError, (errno, strerr):
+        raise PciDeviceParseError(('Failed to locate sysfs mount: %s (%d)' %
+            (PROC_PCI_PATH, strerr, errno)))
+
+    pci_names = os.popen('ls ' + sysfs_mnt + SYSFS_PCI_DEVS_PATH).read().split()
+
+    return pci_names
+
+def get_all_pci_devices():
+    pci_devs = []
+    for pci_name in get_all_pci_names():
+        pci_match = re.match(r"((?P<domain>[0-9a-fA-F]{1,4})[:,])?" + \
+                r"(?P<bus>[0-9a-fA-F]{1,2})[:,]" + \
+                r"(?P<slot>[0-9a-fA-F]{1,2})[.,]" + \
+                r"(?P<func>[0-7])$", pci_name)
+        if pci_match is None:
+            raise PciDeviceParseError(('Failed to parse pci device name: %s' %
+                pci_name))
+        pci_dev_info = pci_match.groupdict('0')
+        domain = parse_hex(pci_dev_info['domain'])
+        bus = parse_hex(pci_dev_info['bus'])
+        slot = parse_hex(pci_dev_info['slot'])
+        func = parse_hex(pci_dev_info['func'])
+        try:
+            pci_dev = PciDevice(domain, bus, slot, func)
+        except:
+            continue
+        pci_devs.append(pci_dev)
+
+    return pci_devs
+
+def create_lspci_info():
+    global lspci_info
+    lspci_info = {}
+
+    # Execute 'lspci' command and parse the result.
+    # If the command does not exist, lspci_info will be kept blank ({}).
+    for paragraph in os.popen(LSPCI_CMD + ' -vmmD').read().split('\n\n'):
+        device_name = None
+        device_info = {}
+        for line in paragraph.split('\n'):
+            try:
+                (opt, value) = line.split(':\t')
+                if opt == 'Slot':
+                    device_name = value
+                else:
+                    device_info[opt] = value
+            except:
+                pass
+        if device_name is not None:
+            lspci_info[device_name] = device_info
 
 class PciDeviceNotFoundError(Exception):
     def __init__(self,domain,bus,slot,func):
@@ -92,7 +164,15 @@ class PciDevice:
         self.subdevice = None
         self.msix = 0
         self.msix_iomem = []
+        self.revision = 0
+        self.classcode = None
+        self.vendorname = ""
+        self.devicename = ""
+        self.classname = ""
+        self.subvendorname = ""
+        self.subdevicename = ""
         self.get_info_from_sysfs()
+        self.get_info_from_lspci()
 
     def find_capability(self, type):
         try:
@@ -208,9 +288,8 @@ class PciDevice:
                 self.name+SYSFS_PCI_DEV_DRIVER_DIR_PATH
         try:
             self.driver = os.path.basename(os.readlink(path))
-        except IOError, (errno, strerr):
-            raise PciDeviceParseError(('Failed to read %s: %s (%d)' %
-                (path, strerr, errno)))
+        except OSError, (errno, strerr):
+            self.driver = ""
 
         path = sysfs_mnt+SYSFS_PCI_DEVS_PATH+'/'+ \
                 self.name+SYSFS_PCI_DEV_VENDOR_PATH
@@ -243,6 +322,36 @@ class PciDevice:
         except IOError, (errno, strerr):
             raise PciDeviceParseError(('Failed to open & read %s: %s (%d)' %
                 (path, strerr, errno)))
+
+        path = sysfs_mnt+SYSFS_PCI_DEVS_PATH+'/'+ \
+                self.name+SYSFS_PCI_DEV_CLASS_PATH
+        try:
+            self.classcode = int(open(path,'r').readline(), 16)
+        except IOError, (errno, strerr):
+            raise PciDeviceParseError(('Failed to open & read %s: %s (%d)' %
+                (path, strerr, errno)))
+
+        return True
+
+    def get_info_from_lspci(self):
+        """ Get information such as vendor name, device name, class name, etc.
+        Since we cannot obtain these data from sysfs, use 'lspci' command.
+        """
+        global lspci_info
+
+        if lspci_info is None:
+            create_lspci_info()
+
+        try:
+            device_info = lspci_info[self.name]
+            self.revision = int(device_info['Rev'], 16)
+            self.vendorname = device_info['Vendor']
+            self.devicename = device_info['Device']
+            self.classname = device_info['Class']
+            self.subvendorname = device_info['SVendor']
+            self.subdevicename = device_info['SDevice']
+        except KeyError:
+            pass
 
         return True
 
diff -r 19970181d6a4 -r fdf409891cd6 tools/python/xen/xend/XendAPI.py
--- a/tools/python/xen/xend/XendAPI.py	Tue Jul 01 14:50:35 2008 +0100
+++ b/tools/python/xen/xend/XendAPI.py	Wed Jul 02 17:59:30 2008 +0900
@@ -40,6 +40,7 @@ from XendVMMetrics import XendVMMetrics
 from XendVMMetrics import XendVMMetrics
 from XendPIF import XendPIF
 from XendPBD import XendPBD
+from XendPPCI import XendPPCI
 from XendXSPolicy import XendXSPolicy, XendACMPolicy
 
 from XendAPIConstants import *
@@ -476,7 +477,8 @@ classes = {
     'PIF'          : valid_object("PIF"),
     'VM_metrics'   : valid_object("VM_metrics"),
     'PBD'          : valid_object("PBD"),
-    'PIF_metrics'  : valid_object("PIF_metrics")
+    'PIF_metrics'  : valid_object("PIF_metrics"),
+    'PPCI'         : valid_object("PPCI"),
 }
 
 autoplug_classes = {
@@ -485,6 +487,7 @@ autoplug_classes = {
     'VM_metrics'  : XendVMMetrics,
     'PBD'         : XendPBD,
     'PIF_metrics' : XendPIFMetrics,
+    'PPCI'        : XendPPCI,
     'XSPolicy'    : XendXSPolicy,
     'ACMPolicy'   : XendACMPolicy,
 }
@@ -874,6 +877,7 @@ class XendAPI(object):
                     'resident_VMs',
                     'PBDs',
                     'PIFs',
+                    'PPCIs',
                     'host_CPUs',
                     'cpu_configuration',
                     'metrics',
@@ -952,6 +956,8 @@ class XendAPI(object):
         return xen_api_success(XendPBD.get_all())
     def host_get_PIFs(self, session, ref):
         return xen_api_success(XendNode.instance().get_PIF_refs())
+    def host_get_PPCIs(self, session, ref):
+        return xen_api_success(XendNode.instance().get_PPCI_refs())
     def host_get_host_CPUs(self, session, host_ref):
         return xen_api_success(XendNode.instance().get_host_cpu_refs())
     def host_get_metrics(self, _, ref):
@@ -1027,7 +1033,8 @@ class XendAPI(object):
                   'sched_policy': node.get_vcpus_policy(),
                   'logging': {},
                   'PIFs': XendPIF.get_all(),
-                  'PBDs': XendPBD.get_all()}
+                  'PBDs': XendPBD.get_all(),
+                  'PPCIs': XendPPCI.get_all()}
         return xen_api_success(record)
 
     # class methods
@@ -1288,7 +1295,7 @@ class XendAPI(object):
     def VM_get_consoles(self, session, vm_ref):
         dom = XendDomain.instance().get_vm_by_uuid(vm_ref)
         return xen_api_success(dom.get_consoles())
-    
+
     def VM_get_tools_version(self, session, vm_ref):
         dom = XendDomain.instance().get_vm_by_uuid(vm_ref)
         return dom.get_tools_version()
diff -r 19970181d6a4 -r fdf409891cd6 tools/python/xen/xend/XendNode.py
--- a/tools/python/xen/xend/XendNode.py	Tue Jul 01 14:50:35 2008 +0100
+++ b/tools/python/xen/xend/XendNode.py	Wed Jul 02 17:59:30 2008 +0900
@@ -21,6 +21,7 @@ import xen.lowlevel.xc
 import xen.lowlevel.xc
 
 from xen.util import Brctl
+from xen.util import pci as PciUtil
 from xen.xend import XendAPIStore
 
 import uuid, arch
@@ -35,6 +36,7 @@ from XendNetwork import *
 from XendNetwork import *
 from XendStateStore import XendStateStore
 from XendMonitor import XendMonitor
+from XendPPCI import XendPPCI
      
 class XendNode:
     """XendNode - Represents a Domain 0 Host."""
@@ -49,6 +51,7 @@ class XendNode:
         * PIF_metrics
         * network
         * Storage Repository
+        * PPCI
         """
         
         self.xc = xen.lowlevel.xc.xc()
@@ -230,6 +233,41 @@ class XendNode:
                 except CreateUnspecifiedAttributeError:
                     log.warn("Error recreating PBD %s", pbd_uuid) 
 
+
+        # Initialise PPCIs
+        saved_ppcis = self.state_store.load_state('ppci')
+        saved_ppci_table = {}
+        if saved_ppcis:
+            for ppci_uuid, ppci_record in saved_ppcis.items():
+                try:
+                    saved_ppci_table[ppci_record['name']] = ppci_uuid
+                except KeyError:
+                    pass
+
+        for pci_dev in PciUtil.get_all_pci_devices():
+            ppci_record = {
+                'domain':                   pci_dev.domain,
+                'bus':                      pci_dev.bus,
+                'slot':                     pci_dev.slot,
+                'func':                     pci_dev.func,
+                'vendor_id':                pci_dev.vendor,
+                'vendor_name':              pci_dev.vendorname,
+                'device_id':                pci_dev.device,
+                'device_name':              pci_dev.devicename,
+                'revision_id':              pci_dev.revision,
+                'class_code':               pci_dev.classcode,
+                'class_name':               pci_dev.classname,
+                'subsystem_vendor_id':      pci_dev.subvendor,
+                'subsystem_vendor_name':    pci_dev.subvendorname,
+                'subsystem_id':             pci_dev.subdevice,
+                'subsystem_name':           pci_dev.subdevicename,
+                'driver':                   pci_dev.driver
+                }
+            # If saved uuid exists, use it. Otherwise create one.
+            ppci_uuid = saved_ppci_table.get(pci_dev.name, uuid.createString())
+            XendPPCI(ppci_uuid, ppci_record)
+
+
 ##    def network_destroy(self, net_uuid):
  ##       del self.networks[net_uuid]
   ##      self.save_networks()
@@ -270,6 +308,15 @@ class XendNode:
 
 ##         del self.pifs[pif_uuid]
 ##         self.save_PIFs()
+
+
+    def get_PPCI_refs(self):
+        return XendPPCI.get_all()
+
+    def get_ppci_by_uuid(self, ppci_uuid):
+        if ppci_uuid in self.get_PPCI_refs():
+            return ppci_uuid
+        return None
 
 
     def save(self):
@@ -284,6 +331,7 @@ class XendNode:
         self.save_networks()
         self.save_PBDs()
         self.save_SRs()
+        self.save_PPCIs()
 
     def save_PIFs(self):
         pif_records = dict([(pif_uuid, XendAPIStore.get(
@@ -307,6 +355,12 @@ class XendNode:
         sr_records = dict([(k, v.get_record(transient = False))
                             for k, v in self.srs.items()])
         self.state_store.save_state('sr', sr_records)
+
+    def save_PPCIs(self):
+        ppci_records = dict([(ppci_uuid, XendAPIStore.get(
+                                 ppci_uuid, "PPCI").get_record())
+                            for ppci_uuid in XendPPCI.get_all()])
+        self.state_store.save_state('ppci', ppci_records)
 
     def shutdown(self):
         return 0
diff -r 19970181d6a4 -r fdf409891cd6 tools/python/xen/xend/XendPPCI.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/python/xen/xend/XendPPCI.py	Wed Jul 02 17:59:30 2008 +0900
@@ -0,0 +1,158 @@
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#============================================================================
+# Copyright (c) 2008 NEC Corporation
+#       Yosuke Iwamatsu <y-iwamatsu at ab jp nec com>
+#============================================================================
+
+from xen.xend.XendBase import XendBase
+from xen.xend.XendBase import XendAPIStore
+from xen.xend import uuid as genuuid
+
+class XendPPCI(XendBase):
+    """Representation of a physical PCI device."""
+
+    def getClass(self):
+        return "PPCI"
+
+    def getAttrRO(self):
+        attrRO = ['host',
+                  'domain',
+                  'bus',
+                  'slot',
+                  'func',
+                  'name',
+                  'vendor_id',
+                  'vendor_name',
+                  'device_id',
+                  'device_name',
+                  'revision_id',
+                  'class_code',
+                  'class_name',
+                  'subsystem_vendor_id',
+                  'subsystem_vendor_name',
+                  'subsystem_id',
+                  'subsystem_name',
+                  'driver']
+        return XendBase.getAttrRO() + attrRO
+
+    def getAttrRW(self):
+        attrRW = []
+        return XendBase.getAttrRW() + attrRW
+
+    def getAttrInst(self):
+        attrInst = []
+        return XendBase.getAttrInst() + attrInst
+
+    def getMethods(self):
+        methods = []
+        return XendBase.getMethods() + methods
+
+    def getFuncs(self):
+        funcs = []
+        return XendBase.getFuncs() + funcs
+
+    getClass    = classmethod(getClass)
+    getAttrRO   = classmethod(getAttrRO)
+    getAttrRW   = classmethod(getAttrRW)
+    getAttrInst = classmethod(getAttrInst)
+    getMethods  = classmethod(getMethods)
+    getFuncs    = classmethod(getFuncs)
+ 
+    def get_by_sbdf(self, domain, bus, slot, func):
+        for ppci in XendAPIStore.get_all("PPCI"):
+            if ppci.get_domain() == int(domain, 16) and \
+               ppci.get_bus() == int(bus, 16) and \
+               ppci.get_slot() == int(slot, 16) and \
+               ppci.get_func() == int(func, 16):
+                return ppci.get_uuid()
+        return None
+
+    get_by_sbdf = classmethod(get_by_sbdf)
+
+    def __init__(self, uuid, record):
+        self.domain = record['domain']
+        self.bus = record['bus']
+        self.slot = record['slot']
+        self.func = record['func']
+        self.vendor_id = record['vendor_id']
+        self.vendor_name = record['vendor_name']
+        self.device_id = record['device_id']
+        self.device_name = record['device_name']
+        self.revision_id = record['revision_id']
+        self.class_code = record['class_code']
+        self.class_name = record['class_name']
+        self.subsystem_vendor_id = record['subsystem_vendor_id']
+        self.subsystem_vendor_name = record['subsystem_vendor_name']
+        self.subsystem_id = record['subsystem_id']
+        self.subsystem_name = record['subsystem_name']
+        self.driver = record['driver']
+        XendBase.__init__(self, uuid, record)
+
+    def get_host(self):
+        from xen.xend import XendNode
+        return XendNode.instance().get_uuid()
+
+    def get_domain(self):
+        return self.domain
+
+    def get_bus(self):
+        return self.bus
+
+    def get_slot(self):
+        return self.slot
+
+    def get_func(self):
+        return self.func
+
+    def get_name(self):
+        return "%04x:%02x:%02x.%01x" % (self.domain, self.bus, self.slot,
+                                        self.func)
+
+    def get_vendor_id(self):
+        return self.vendor_id
+
+    def get_vendor_name(self):
+        return self.vendor_name
+
+    def get_device_id(self):
+        return self.device_id
+
+    def get_device_name(self):
+        return self.device_name
+
+    def get_class_code(self):
+        return self.class_code
+
+    def get_class_name(self):
+        return self.class_name
+
+    def get_revision_id(self):
+        return self.revision_id
+
+    def get_subsystem_vendor_id(self):
+        return self.subsystem_vendor_id
+
+    def get_subsystem_vendor_name(self):
+        return self.subsystem_vendor_name
+
+    def get_subsystem_id(self):
+        return self.subsystem_id
+
+    def get_subsystem_name(self):
+        return self.subsystem_name
+
+    def get_driver(self):
+        return self.driver
+
