Skip to content

Commit

Permalink
Add EDD 4 code and some logging cleanups.
Browse files Browse the repository at this point in the history
This adds support for EDD4 types and fields (some of which the kernel
does not yet expose), as well as only logging relevant edd information.

Here's an example log from a system with good EDD data booted from USB,
installing to a locally connected SATA disk:

15:16:01,198 INFO blivet: edd: collected mbr signatures: {u'sda': '0x00022a9f', u'sdb': '0x946a5e4b'}
15:16:01,199 WARN blivet: edd: interface type USB is not implemented (/sys/firmware/edd/int13_dev80)
15:16:01,199 DEBUG blivet: edd: data extracted from 0x80:
	path: /sys/firmware/edd/int13_dev80, version: 0x21
	type: USB, mbr_signature: 0x946a5e4b, sectors: 15667200
	host_bus: PCI pci_dev: ff:ff.255 channel: 255
	usb_serial: 808463921
15:16:01,199 INFO blivet: edd: matched 0x80 to sdb using MBR sig
15:16:01,202 DEBUG blivet: edd: data extracted from 0x81:
	path: /sys/firmware/edd/int13_dev81, version: 0x30
	type: SATA, mbr_signature: 0x00022a9f, sectors: 312581808
	sysfs pci path: ../devices/pci0000:00/0000:00:1f.2/ata1/host1/target1:0:0/1:0:0:0/block/sda
	host_bus: PCI pci_dev: 00:1f.2 channel: 255
	ata_device: 0
15:16:01,202 INFO blivet: edd: matched 0x81 to sda using pci_dev

Isn't that nice?  It is nice.

Signed-off-by: Peter Jones <pjones@redhat.com>
  • Loading branch information
vathpela committed Oct 7, 2015
1 parent c24fec7 commit 90a46e4
Showing 1 changed file with 229 additions and 30 deletions.
259 changes: 229 additions & 30 deletions blivet/devicelibs/edd.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,60 +34,238 @@

log = logging.getLogger("blivet")

re_host_bus = re.compile(r'^PCI\S*\s*(\S*)\s*channel: (\S*)\s*$')
re_interface_scsi = re.compile(r'^SCSI\s*id: (\S*)\s*lun: (\S*)\s*$')
re_host_bus_pci = re.compile(r'^(PCIX|PCI|XPRS|HTPT)\s*(\S*)\s*channel: (\S*)\s*$')
re_interface_atapi = re.compile(r'^ATAPI\s*device: (\S*)\s*lun: (\S*)\s*$')
re_interface_ata = re.compile(r'^ATA\s*device: (\S*)\s*$')
re_interface_sata = re.compile(r'^SATA\s*device: (\S*)\s*$')
re_interface_scsi = re.compile(r'^SCSI\s*id: (\S*)\s*lun: (\S*)\s*$')
re_interface_usb = re.compile(r'^USB\s*serial_number: (\S*)\s*$')
re_interface_1394 = re.compile(r'^1394\s*eui: (\S*)\s*$')
re_interface_fibre = re.compile(r'^FIBRE\s*wwid: (\S*)\s*lun: (\S*)\s*$')
re_interface_i2o = re.compile(r'^I2O\s*identity_tag: (\S*)\s*$')
# pretty sure the RAID definition using "identity_tag" is basically a kernel
# bug, but it's part of the ABI now, so it sticks. The format of the
# scnprintf() is at least correct.
re_interface_raid = re.compile(r'^RAID\s*identity_tag: (\S*)\s*$')
re_interface_edd3_sata = re.compile(r'^SATA\s*device: (\S*)\s*$')
# EDD 4 features from 2010 and later. Awesomely, the "version" output from
# int 13 AH=41h says: AH Version of extensions. Shall be set to 30h,
# so there's no way to distinguish these from EDD 3, even thuogh SATA does
# differ. In theory, if we're on <4.0, pmp should always be all 0's.
re_interface_edd4_sata = re.compile(r'^SATA\s*device: (\S*)\s*pmp: (\S*)\s*$')
re_interface_sas = re.compile(r'^SAS\s*sas_address: (\S*)\s*lun: \(\S*\)\s*$')
# to make life difficult, when it finds an unknown interface type string,
# the kernel prints the values without the string. But it does print the
# anchoring tab that would go between them...
re_interface_unknown = re.compile(r'^(\S*)\s*unknown: (\S*) (\S*)\s*$')

class EddEntry(object):
""" This object merely collects what the /sys/firmware/edd/* entries can
provide.
"""
def __init__(self, sysfspath):
self.type = None
# some misc data from various files...
self.sysfspath = sysfspath
""" sysfspath is the path we're probing
"""

self.sysfslink = None
""" The path /sys/block/BLAH is a symlink link to once we've resolved
that this is a particular device. Used for logging later.
"""

self.version = util.get_sysfs_attr(sysfspath, "version")
""" The edd version this entry claims conformance with, from
/sys/firmware/edd/int13_devXX/version """

self.ata_device = None
self.channel = None
self.mbr_sig = None
""" The MBR signature data from edd/int13_devXX/mbr_signature """

self.sectors = None
""" The number of sectors on the device from edd/int13_devXX/sectors """

# Now the data from edd/int13_devXX/host_bus
self.host_bus = None
""" The ID string for the host bus type, from
edd/int13_devXX/host_bus.
"""
self.pci_dev = None
""" The host bus bus:device.function, from edd/int13_devXX/host_bus.
"""
self.channel = None
""" The host bus device's channel number edd/int13_devXX/host_bus.
The spec says:
Channel number. If more than one interface of the same type is
accessed through a single Bus, Slot, Function, then the channel
number shall identify each interface. If there is only one
interface, the content of this field shall be cleared to zero. If
there are two interfaces, such as an ATA Primary and Secondary
interface, the primary interface shall be zero, and the secondary
interface shall be one.
Values 00h through FEh shall represent a valid Channel Number.
Value FFh shall indicate that this field is not used.
If the device is connected to a SATA controller functioning in
non-PATA emulation mode, this byte shall be FFh.
"""

# And now the various data from different formats of
# edd/int13_devXX/interface .
self.interface = None
""" interface is the actual contents of the interface file,
preserved for logging later.
"""

self.type = None
""" The device type from edd/int13_devXX/interface.
"""

self.atapi_device = None
""" The device number of the ATAPI device from
edd/int13_devXX/interface when self.type is ATAPI.
"""
self.atapi_lun = None
""" The LUN of the ATAPI device from edd/int13_devXX/interface when
self.type is ATAPI.
"""

self.ata_device = None
""" The device number from edd/int13_devXX/interface when self.type
is ATA or SATA (because Linux treats these the same.)
"""
self.ata_pmp = None
""" The ATA port multiplier ID from edd/int13_devXX/interface when
self.type is SATA.
"""

self.scsi_id = None
""" The SCSI device ID from edd/int13_devXX/interface when
self.type is SCSI
"""
self.scsi_lun = None
self.sectors = None
self.sysfslink = ""
self.sysfspath = sysfspath
self.version = util.get_sysfs_attr(sysfspath, "version")
""" The SCSI device LUN from edd/int13_devXX/interface when
self.type is SCSI
"""

self.usb_serial = None
""" The USB storage device's serial number from
edd/int13_devXX/interface when self.type is USB.
"""

self.ieee1394_eui64 = None
""" The Firewire/IEEE-1394 EUI-64 ID from edd/int13_devXX/interface
when self.type is 1394.
"""

self.fibre_wwid = None
""" The FibreChannel WWID from edd/int13_devXX/interface when
self.type is FIBRE.
"""
self.fibre_lun = None
""" The FibreChannel LUN from edd/int13_devXX/interface when
self.type is FIBRE.
"""

self.i2o_identity = None
""" The I2O Identity from edd/int13_devXX/interface when self.type
is I2O.
"""

self.sas_address = None
""" The SAS Address from edd/int13_devXX/interface when self.type
is SAS.
"""
self.sas_lun = None
""" The SAS LUN from edd/int13_devXX/interface when self.type is SAS.
"""

self.load()

def __str__(self):
return \
"\tpath: %(sysfspath)s version: %(version)s\n" \
"\tsysfs pci path: %(sysfslink)s\n" \
"\ttype: %(type)s, ata_device: %(ata_device)s\n" \
"\tchannel: %(channel)s, mbr_signature: %(mbr_sig)s\n" \
"\tpci_dev: %(pci_dev)s, scsi_id: %(scsi_id)s," \
" scsi_lun: %(scsi_lun)s, sectors: %(sectors)s" % self.__dict__
s = "\tpath: %(sysfspath)s, version: %(version)s\n" \
"\ttype: %(type)s, mbr_signature: %(mbr_sig)s, " \
"sectors: %(sectors)s"
if self.sysfslink != None:
s += "\n\tsysfs pci path: %(sysfslink)s"
if any([self.host_bus, self.pci_dev, self.channel]):
s += "\n\thost_bus: %(host_bus)s pci_dev: %(pci_dev)s "\
"channel: %(channel)s"
if any([self.atapi_device, self.atapi_lun]):
s += "\n\tatapi_device: %(atapi_device)s, atapi_lun: %(atapi_lun)s"
if self.ata_device != None:
s += "\n\tata_device: %(ata_device)s"
if self.ata_pmp != None:
s += ", ata_pmp: %(ata_pmp)s"
if any([self.scsi_id, self.scsi_lun]):
s += "\n\tscsi_id: %(scsi_id)s, scsi_lun: %(scsi_lun)s"
if self.usb_serial != None:
s += "\n\tusb_serial: %(usb_serial)s"
if self.ieee1394_eui64 != None:
s += "\n\t1394_eui: %(ieee1394_eui64)"
if any([self.fibre_wwid,self.fibre_lun]):
s += "\n\tfibre wwid: %(fibre_wwid)s, lun: %s(fibre_lun)s"
if self.i2o_identity != None:
s += "\n\ti2o_identity: %(i2o_identity)s"
if any([self.sas_address, self.sas_lun]):
s += "\n\tsas_address: %(sas_address)s sas_lun: %(sas_lun)s"

return s % self.__dict__

def load(self):
interface = util.get_sysfs_attr(self.sysfspath, "interface")
# save this so we can log it from the matcher.
self.interface = interface
if interface:
try:
self.type = interface.split()[0]
unsupported = ("ATAPI", "USB", "1394", "FIBRE", "I2O", "RAID")
if self.type in unsupported:
log.warning("edd: interface type %s is not implemented (%s)",
self.type, self.sysfspath)
log.warning("edd: interface details: %s", interface)
if self.type == "ATAPI":
match = re_interface_atapi.match(interface)
self.atapi_device = int(match.group(1))
self.atapi_lun = int(match.group(2))
elif self.type == "ATA":
match = re_interface_ata.match(interface)
self.ata_device = int(match.group(1))
elif self.type == "SCSI":
match = re_interface_scsi.match(interface)
self.scsi_id = int(match.group(1))
self.scsi_lun = int(match.group(2))
elif self.type == "USB":
match = re_interface_usb.match(interface)
self.usb_serial = int(match.group(1), base=16)
elif self.type == "1394":
match = re_interface_1394.match(interface)
self.ieee1394_eui64 = int(match.group(1), base=16)
elif self.type == "FIBRE":
match = re_interface_fibre.match(interface)
self.fibre_wwid = int(match.group(1), base=16)
self.fibre_lun = int(match.group(2), base=16)
elif self.type == "I2O":
match = re_interface_i2o.match(interface)
self.i2o_identity = int(match.group(1), base=16)
elif self.type == "RAID":
match = re_interface_raid.match(interface)
self.raid_array = int(match.group(1), base=16)
elif self.type == "SATA":
match = re_interface_sata.match(interface)
self.ata_device = int(match.group(1))
match = re_interface_edd4_sata.match(interface)
if match:
self.ata_device = int(match.group(1))
self.ata_pmp = int(match.group(2))
else:
match = re_interface_edd3_sata.match(interface)
self.ata_device = int(match.group(1))
elif self.type == "SAS":
sas_match = re_interface_sas.match(interface)
unknown_match = re_interface_unknown.match(interface)
if sas_match:
self.sas_address = int(sas_match.group(1), base=16)
self.sas_lun = int(sas_match.group(2), base=16)
elif unknown_match:
self.sas_address = int(unknown_match.group(1), base=16)
self.sas_lun = int(unknown_match.group(2), base=16)
else:
log.warning("edd: can not match interface for %s: %s",
self.sysfspath, interface)
else:
log.warning("edd: can not match interface for %s: %s",
self.sysfspath, interface)
Expand All @@ -104,10 +282,11 @@ def load(self):
self.sectors = int(sectors)
hbus = util.get_sysfs_attr(self.sysfspath, "host_bus")
if hbus:
match = re_host_bus.match(hbus)
match = re_host_bus_pci.match(hbus)
if match:
self.pci_dev = match.group(1)
self.channel = int(match.group(2))
self.host_bus = match.group(1)
self.pci_dev = match.group(2)
self.channel = int(match.group(3))
else:
log.warning("edd: can not match host_bus for %s: %s",
self.sysfspath, hbus)
Expand Down Expand Up @@ -141,6 +320,17 @@ def devname_from_ata_pci_dev(self):
# for ATA (non-SATA) devices. Also in EDD 3, SATA port multipliers
# aren't represented in any way.
#
# In EDD 4, which unfortunately says to leave 0x30 as the version
# number, the port multiplier id is an additional field on the
# interface. So basically we should try to use the value the
# kernel gives us*, but we can't trust it. Thankfully there
# won't be a devX.Y.Z (i.e. a port multiplier device) in sysfs
# that collides with devX.Z (a non-port-multiplied device),
# so if we get a value from the kernel, we can try with and
# without it.
#
# * When the kernel finally learns of these facts...
#
if components[4] != '0000:%s' % (self.edd.pci_dev,):
continue
if not components[5].startswith('ata'):
Expand Down Expand Up @@ -182,8 +372,10 @@ def devname_from_ata_pci_dev(self):
self.edd.sysfslink = link
return path.split('/')[-1]
else:
log.warning("edd: ATA Port multipliers are unsupported")
continue;
pmp = int(match.group(1))
if self.edd.ata_pmp == pmp:
self.edd.sysfslink = link
return path.split('/')[-1]
return None

def devname_from_scsi_pci_dev(self):
Expand All @@ -201,6 +393,7 @@ def devname_from_scsi_pci_dev(self):
name = block_entries[0]
else:
log.warning("edd: directory does not exist: %s", path)
self.edd.sysfslink = path
return name

def devname_from_virt_pci_dev(self):
Expand All @@ -213,14 +406,20 @@ def devname_from_virt_pci_dev(self):
block_entries = os.listdir(matching_paths[0])
if len(block_entries) == 1:
name = block_entries[0]
self.edd.sysfslink = matching_paths[0]
return name

def devname_from_pci_dev(self):
name = self.devname_from_virt_pci_dev()
if not name is None:
return name

unsupported = ("ATAPI", "USB", "1394", "I2O", "RAID", "FIBRE", "SAS")
if self.edd.type in unsupported:
log.warning("edd: interface type %s is not implemented (%s)",
self.edd.type, self.edd.sysfspath)
log.warning("edd: interface details: %s", self.edd.interface)
if self.edd.type in ("ATA", "SATA") and \
self.edd.channel is not None and \
self.edd.ata_device is not None:
name = self.devname_from_ata_pci_dev()
elif self.edd.type == "SCSI":
Expand Down

0 comments on commit 90a46e4

Please sign in to comment.