1#
2# Copyright 2009, 2013 Red Hat, Inc.
3#
4# This work is licensed under the GNU GPLv2 or later.
5# See the COPYING file in the top-level directory.
6
7import os
8
9from .logger import log
10from .xmlbuilder import XMLBuilder, XMLProperty, XMLChildProperty
11
12
13def _compare_int(nodedev_val, hostdev_val):
14    def _intify(val):
15        try:
16            if "0x" in str(val):
17                return int(val or '0x00', 16)
18            else:
19                return int(val)
20        except Exception:
21            return -1
22
23    nodedev_val = _intify(nodedev_val)
24    hostdev_val = _intify(hostdev_val)
25    return (nodedev_val == hostdev_val or hostdev_val == -1)
26
27
28class DevNode(XMLBuilder):
29    XML_NAME = "devnode"
30
31    node_type = XMLProperty("./@type")
32    path = XMLProperty(".")
33
34
35class NodeDevice(XMLBuilder):
36    CAPABILITY_TYPE_NET = "net"
37    CAPABILITY_TYPE_PCI = "pci"
38    CAPABILITY_TYPE_USBDEV = "usb_device"
39    CAPABILITY_TYPE_STORAGE = "storage"
40    CAPABILITY_TYPE_SCSIBUS = "scsi_host"
41    CAPABILITY_TYPE_SCSIDEV = "scsi"
42    CAPABILITY_TYPE_DRM = "drm"
43
44    @staticmethod
45    def lookupNodedevFromString(conn, idstring):
46        """
47        Convert the passed libvirt node device name to a NodeDevice
48        instance, with proper error reporting. If the name is name is not
49        found, we will attempt to parse the name as would be passed to
50        devAddressToNodeDev
51
52        :param conn: libvirt.virConnect instance to perform the lookup on
53        :param idstring: libvirt node device name to lookup, or address
54            of the form:
55            - bus.addr (ex. 001.003 for a usb device)
56            - vendor:product (ex. 0x1234:0x5678 for a usb device
57            - (domain:)bus:slot.func (ex. 00:10.0 for a pci device)
58
59        :returns: NodeDevice instance
60        """
61        # First try and see if this is a libvirt nodedev name
62        for nodedev in conn.fetch_all_nodedevs():
63            if nodedev.name == idstring:
64                return nodedev
65
66        try:
67            return _AddressStringToNodedev(conn, idstring)
68        except Exception:
69            log.debug("Error looking up nodedev from idstring=%s",
70                idstring, exc_info=True)
71            raise
72
73
74    XML_NAME = "device"
75
76    # Libvirt can generate bogus 'system' XML:
77    # https://bugzilla.redhat.com/show_bug.cgi?id=1184131
78    _XML_SANITIZE = True
79
80    name = XMLProperty("./name")
81    parent = XMLProperty("./parent")
82    device_type = XMLProperty("./capability/@type")
83
84    def compare_to_hostdev(self, hostdev):
85        if self.device_type == "pci":
86            if hostdev.type != "pci":
87                return False
88
89            return (_compare_int(self.domain, hostdev.domain) and
90                _compare_int(self.bus, hostdev.bus) and
91                _compare_int(self.slot, hostdev.slot) and
92                _compare_int(self.function, hostdev.function))
93
94        if self.device_type == "usb_device":
95            if hostdev.type != "usb":
96                return False
97
98            return (_compare_int(self.product_id, hostdev.product) and
99                _compare_int(self.vendor_id, hostdev.vendor) and
100                _compare_int(self.bus, hostdev.bus) and
101                _compare_int(self.device, hostdev.device))
102
103        return False
104
105
106    ########################
107    # XML helper functions #
108    ########################
109
110    def is_pci_sriov(self):
111        return self._capability_type == "virt_functions"
112    def is_pci_bridge(self):
113        return self._capability_type == "pci-bridge"
114
115    def is_usb_linux_root_hub(self):
116        return (self.vendor_id == "0x1d6b" and
117                self.product_id in ["0x0001", "0x0002", "0x0003"])
118
119    def is_drm_render(self):
120        return self.device_type == "drm" and self.drm_type == "render"
121
122
123    ##################
124    # XML properties #
125    ##################
126
127    # type='net' options
128    interface = XMLProperty("./capability/interface")
129
130    # type='pci' options
131    domain = XMLProperty("./capability/domain")
132    bus = XMLProperty("./capability/bus")
133    slot = XMLProperty("./capability/slot")
134    function = XMLProperty("./capability/function")
135    product_name = XMLProperty("./capability/product")
136    vendor_name = XMLProperty("./capability/vendor")
137    _capability_type = XMLProperty("./capability/capability/@type")
138
139    # type='usb' options
140    device = XMLProperty("./capability/device")
141    product_id = XMLProperty("./capability/product/@id")
142    vendor_id = XMLProperty("./capability/vendor/@id")
143
144    # type='scsi' options
145    host = XMLProperty("./capability/host")
146    target = XMLProperty("./capability/target")
147    lun = XMLProperty("./capability/lun")
148
149    # type='storage' options
150    block = XMLProperty("./capability/block")
151    drive_type = XMLProperty("./capability/drive_type")
152
153    media_label = XMLProperty(
154        "./capability/capability[@type='removable']/media_label")
155    media_available = XMLProperty(
156            "./capability/capability[@type='removable']/media_available",
157            is_int=True)
158
159    # type='drm' options
160    drm_type = XMLProperty("./capability/type")
161    devnodes = XMLChildProperty(DevNode)
162
163    def get_devnode(self, parent="by-path"):
164        for d in self.devnodes:
165            paths = d.path.split(os.sep)
166            if len(paths) > 2 and paths[-2] == parent:
167                return d
168        if len(self.devnodes) > 0:
169            return self.devnodes[0]
170
171
172def _AddressStringToHostdev(conn, addrstr):
173    from .devices import DeviceHostdev
174    hostdev = DeviceHostdev(conn)
175
176    try:
177        # Determine addrstr type
178        if addrstr.count(":") in [1, 2] and "." in addrstr:
179            addrstr, func = addrstr.split(".", 1)
180            addrstr, slot = addrstr.rsplit(":", 1)
181            domain = "0"
182            if ":" in addrstr:
183                domain, bus = addrstr.split(":", 1)
184            else:
185                bus = addrstr
186
187            hostdev.type = "pci"
188            hostdev.domain = "0x%.4X" % int(domain, 16)
189            hostdev.function = "0x%.2X" % int(func, 16)
190            hostdev.slot = "0x%.2X" % int(slot, 16)
191            hostdev.bus = "0x%.2X" % int(bus, 16)
192
193        elif ":" in addrstr:
194            vendor, product = addrstr.split(":")
195
196            hostdev.type = "usb"
197            hostdev.vendor = "0x%.4X" % int(vendor, 16)
198            hostdev.product = "0x%.4X" % int(product, 16)
199
200        elif "." in addrstr:
201            bus, device = addrstr.split(".", 1)
202
203            hostdev.type = "usb"
204            hostdev.bus = bus
205            hostdev.device = device
206        else:
207            raise RuntimeError("Unknown address type")
208    except Exception:
209        log.debug("Error parsing node device string.", exc_info=True)
210        raise
211
212    return hostdev
213
214
215def _AddressStringToNodedev(conn, addrstr):
216    hostdev = _AddressStringToHostdev(conn, addrstr)
217
218    # Iterate over node devices and compare
219    count = 0
220    nodedev = None
221
222    for xmlobj in conn.fetch_all_nodedevs():
223        if xmlobj.compare_to_hostdev(hostdev):
224            nodedev = xmlobj
225            count += 1
226
227    if count == 1:
228        return nodedev
229    elif count > 1:
230        raise ValueError(_("%s corresponds to multiple node devices") %
231                         addrstr)
232    elif count < 1:
233        raise ValueError(_("Did not find a matching node device for '%s'") %
234                         addrstr)
235