Re: [Autotest] [PATCH] KVM test: Add PCI pass through test

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Oct 15, 2009 at 3:45 AM, Yolkfull Chow <yzhou@xxxxxxxxxx> wrote:
> On Wed, Oct 14, 2009 at 09:08:00AM -0300, Lucas Meneghel Rodrigues wrote:
>> Add a new PCI pass trough test. It supports both SR-IOV virtual
>> functions and physical NIC card pass through.
>>
>> Single Root I/O Virtualization (SR-IOV) allows a single PCI device to
>> be shared amongst multiple virtual machines while retaining the
>> performance benefit of assigning a PCI device to a virtual machine.
>> A common example is where a single SR-IOV capable NIC - with perhaps
>> only a single physical network port - might be shared with multiple
>> virtual machines by assigning a virtual function to each VM.
>>
>> SR-IOV support is implemented in the kernel. The core implementation is
>> contained in the PCI subsystem, but there must also be driver support
>> for both the Physical Function (PF) and Virtual Function (VF) devices.
>> With an SR-IOV capable device one can allocate VFs from a PF. The VFs
>> surface as PCI devices which are backed on the physical PCI device by
>> resources (queues, and register sets).
>>
>> Device support:
>>
>> In 2.6.30, the Intel® 82576 Gigabit Ethernet Controller is the only
>> SR-IOV capable device supported. The igb driver has PF support and the
>> igbvf has VF support.
>>
>> In 2.6.31 the Neterion® X3100™ is supported as well. This device uses
>> the same vxge driver for the PF as well as the VFs.
>
> Wow, new NIC card supports SR-IOV...
> At this rate, do we need to move the driver name and its parameter into
> config file so that in future if a new NIC card using different driver
> is supported, we could handle it without changing code ?

Absolutely, it didn't occur to me at first, but yes, we ought to move
the driver name and parameters to the config file.

>>
>> In order to configure the test:
>>
>>   * For SR-IOV virtual functions passthrough, we could specify the
>>     module parameter 'max_vfs' in config file.
>>   * For physical NIC card pass through, we should specify the device
>>     name(s).
>>
>> Signed-off-by: Yolkfull Chow <yzhou@xxxxxxxxxx>
>> ---
>>  client/tests/kvm/kvm_tests.cfg.sample |   11 ++-
>>  client/tests/kvm/kvm_utils.py         |  278 +++++++++++++++++++++++++++++++++
>>  client/tests/kvm/kvm_vm.py            |   72 +++++++++
>>  3 files changed, 360 insertions(+), 1 deletions(-)
>>
>> diff --git a/client/tests/kvm/kvm_tests.cfg.sample b/client/tests/kvm/kvm_tests.cfg.sample
>> index cc3228a..1dad188 100644
>> --- a/client/tests/kvm/kvm_tests.cfg.sample
>> +++ b/client/tests/kvm/kvm_tests.cfg.sample
>> @@ -786,13 +786,22 @@ variants:
>>          only default
>>          image_format = raw
>>
>> -
>>  variants:
>>      - @smallpages:
>>      - hugepages:
>>          pre_command = "/usr/bin/python scripts/hugepage.py /mnt/kvm_hugepage"
>>          extra_params += " -mem-path /mnt/kvm_hugepage"
>>
>> +variants:
>> +    - @no_passthrough:
>> +        pass_through = no
>> +    - nic_passthrough:
>> +        pass_through = pf
>> +        passthrough_devs = eth1
>> +    - vfs_passthrough:
>> +        pass_through = vf
>> +        max_vfs = 7
>> +        vfs_count = 7
>>
>>  variants:
>>      - @basic:
>> diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
>> index 53b664a..0e3398c 100644
>> --- a/client/tests/kvm/kvm_utils.py
>> +++ b/client/tests/kvm/kvm_utils.py
>> @@ -788,3 +788,281 @@ def md5sum_file(filename, size=None):
>>          size -= len(data)
>>      f.close()
>>      return o.hexdigest()
>> +
>> +
>> +def get_full_id(pci_id):
>> +    """
>> +    Get full PCI ID of pci_id.
>> +    """
>> +    cmd = "lspci -D | awk '/%s/ {print $1}'" % pci_id
>> +    status, full_id = commands.getstatusoutput(cmd)
>> +    if status != 0:
>> +        return None
>> +    return full_id
>> +
>> +
>> +def get_vendor_id(pci_id):
>> +    """
>> +    Check out the device vendor ID according to PCI ID.
>> +    """
>> +    cmd = "lspci -n | awk '/%s/ {print $3}'" % pci_id
>> +    return re.sub(":", " ", commands.getoutput(cmd))
>> +
>> +
>> +def release_dev(pci_id, pci_dict):
>> +    """
>> +    Release a single PCI device.
>> +
>> +    @param pci_id: PCI ID of a given PCI device
>> +    @param pci_dict: Dictionary with information about PCI devices
>> +    """
>> +    base_dir = "/sys/bus/pci"
>> +    full_id = get_full_id(pci_id)
>> +    vendor_id = get_vendor_id(pci_id)
>> +    drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id)
>> +    if 'pci-stub' in os.readlink(drv_path):
>> +        cmd = "echo '%s' > %s/new_id" % (vendor_id, drv_path)
>> +        if os.system(cmd):
>> +            return False
>> +
>> +        stub_path = os.path.join(base_dir, "drivers/pci-stub")
>> +        cmd = "echo '%s' > %s/unbind" % (full_id, stub_path)
>> +        if os.system(cmd):
>> +            return False
>> +
>> +        prev_driver = pci_dict[pci_id]
>> +        cmd = "echo '%s' > %s/bind" % (full_id, prev_driver)
>> +        if os.system(cmd):
>> +            return False
>> +    return True
>> +
>> +
>> +def release_pci_devs(pci_dict):
>> +    """
>> +    Release all PCI devices assigned to host.
>> +
>> +    @param pci_dict: Dictionary with information about PCI devices
>> +    """
>> +    for pci_id in pci_dict:
>> +        if not release_dev(pci_id, pci_dict):
>> +            logging.error("Failed to release device [%s] to host" % pci_id)
>> +        else:
>> +            logging.info("Release device [%s] successfully" % pci_id)
>> +
>> +
>> +class PassThrough(object):
>> +    """
>> +    Request passthroughable devices on host. It will check whether to request
>> +    PF (physical NIC cards) or VF (Virtual Functions).
>> +    """
>> +    def __init__(self, type="nic_vf", max_vfs=None, names=None):
>> +        """
>> +        Initialize parameter 'type' which could be:
>> +        nic_vf: Virtual Functions
>> +        nic_pf: Physical NIC card
>> +        mixed:  Both includes VFs and PFs
>> +
>> +        If pass through Physical NIC cards, we need to specify which devices
>> +        to be assigned, e.g. 'eth1 eth2'.
>> +
>> +        If pass through Virtual Functions, we need to specify how many vfs
>> +        are going to be assigned, e.g. passthrough_count = 8 and max_vfs in
>> +        config file.
>> +
>> +        @param type: Pass through device's type
>> +        @param max_vfs: parameter of module 'igb'
>> +        @param names: Physical NIC cards' names, e.g.'eth1 eth2 ...'
>> +        """
>> +        self.type = type
>> +        if max_vfs:
>> +            self.max_vfs = int(max_vfs)
>> +        if names:
>> +            self.name_list = names.split()
>> +
>> +
>> +    def sr_iov_setup(self):
>> +        """
>> +        Setup SR-IOV environment, check if module 'igb' is loaded with
>> +        parameter 'max_vfs'.
>> +        """
>> +        re_probe = False
>> +        # Check whether the module 'igb' is loaded
>> +        s, o = commands.getstatusoutput('lsmod | grep igb')
>> +        if s:
> I would take extra consideration that here could also happen timeout
> and return status is None problem. Could change to:
>           if s != 0:

Fair enough, will change it!

>> +            re_probe = True
>> +        elif not self.chk_vfs_count():
>> +            os.system("modprobe -r igb")
>> +            re_probe = True
>> +
>> +        # Re-probe module 'igb'
>> +        if re_probe:
>> +            cmd = "modprobe igb max_vfs=%d" % self.max_vfs
>> +            s, o = commands.getstatusoutput(cmd)
>> +            if s:
>
> ==>            if s != 0:

Idem

>> +                return False
>> +            if not self.chk_vfs_count():
>> +                return False
>> +        return True
>> +
>> +
>> +    def get_vfs_count(self):
>> +        """
>> +        Get Vfs count number according to 'max_vfs'
>> +        """
>> +        cmd = "lspci | grep 'Virtual Function' | wc -l"
>> +        return int(commands.getoutput(cmd))
>> +
>> +
>> +    def chk_vfs_count(self):
>> +        """
>> +        Check VFs count number according to parameter 'max_vfs'.
>> +        """
>> +        if self.get_vfs_count() != (self.max_vfs * 2):
>> +            return False
>> +        return True
>> +
>> +
>> +    def _get_pci_id(self, name, search_str):
>> +        """
>> +        Get the device's PCI ID according to name.
>> +        """
>> +        cmd = "ethtool -i %s | awk '/bus-info/ {print $2}'" % name
>> +        s, pci_id = commands.getstatusoutput(cmd)
>> +        if not (s or "Cannot get driver information" in pci_id):
>> +            return pci_id[5:]
>> +
>> +        cmd = "lspci | awk '/%s/ {print $1}'" % search_str
>> +        pci_ids = [id for id in commands.getoutput(cmd).splitlines()]
>> +        nic_id = int(re.search('[0-9]+', name).group(0))
>> +        if (len(pci_ids) - 1) < nic_id:
>> +            return None
>> +        return pci_ids[nic_id]
>> +
>> +
>> +    def _write_to_file(self, content, file_path):
>> +        """
>> +        Write some content to a file.
>> +
>> +        @param content: Content to be written.
>> +        @param file_path: Path for the file.
>> +        """
>> +        success = False
>> +        try:
>> +            file = open(file_path, 'w')
>> +        except IOError:
>> +            return success
>> +
>> +        try:
>> +            file.write(content)
>> +            success = True
>> +        except IOError:
>> +            pass
>> +        finally:
>> +            file.close()
>> +
>> +        return success
>> +
>> +
>> +    def is_binded_to_stub(self, full_id):
>> +        """
>> +        Verify whether the device with full_id is already binded to pci-stub.
>> +        """
>> +        base_dir = "/sys/bus/pci"
>> +        stub_path = os.path.join(base_dir, "drivers/pci-stub")
>> +        if os.path.exists(os.path.join(stub_path, full_id)):
>> +            return True
>> +        return False
>> +
>> +
>> +    def request_devs(self, count, pt_file, setup=True):
>> +        """
>> +        Implement setup process: unbind the PCI device and then bind it
>> +        to pci-stub driver.
>> +
>> +        @return: a list of successfully requested devices' PCI IDs.
>> +        """
>> +        base_dir = "/sys/bus/pci"
>> +        stub_path = os.path.join(base_dir, "drivers/pci-stub")
>> +
>> +        self.pci_ids = self.catch_devs(int(count))
>> +        logging.debug("Catch_devs has found pci_ids: %s" % self.pci_ids)
>> +
>> +        if setup:
>> +            requested_pci_ids = []
>> +            logging.debug("Going to setup devices: %s" % self.pci_ids)
>> +            dev_prev_drivers = {}
>> +
>> +            # Setup all devices specified for passthrough to guest
>> +            for pci_id in self.pci_ids:
>> +                full_id = get_full_id(pci_id)
>> +                if not full_id:
>> +                    continue
>> +                drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id)
>> +                dev_prev_driver= os.path.realpath(os.path.join(drv_path,
>> +                                                  os.readlink(drv_path)))
>> +                dev_prev_drivers[pci_id] = dev_prev_driver
>> +
>> +                # Judge whether the device driver has been binded to stub
>> +                if not self.is_binded_to_stub(full_id):
>> +                    logging.debug("Setting up device: %s" % full_id)
>> +                    stub_new_id = os.path.join(stub_path, 'new_id')
>> +                    unbind_dev = os.path.join(drv_path, 'unbind')
>> +                    stub_bind = os.path.join(stub_path, 'bind')
>> +
>> +                    vendor_id = get_vendor_id(pci_id)
>> +                    if not self._write_to_file(vendor_id, stub_new_id):
>> +                        continue
>> +                    if not self._write_to_file(full_id, unbind_dev):
>> +                        continue
>> +                    if not self._write_to_file(full_id, stub_bind):
>> +                        continue
>> +                    if not self.is_binded_to_stub(full_id):
>> +                        logging.error("Request device failed: %s" % pci_id)
>> +                        continue
>> +                else:
>> +                    logging.debug("Device [%s] already binded to stub" % pci_id)
>> +                requested_pci_ids.append(pci_id)
>> +            cPickle.dump(dev_prev_drivers, pt_file)
>> +            self.pci_ids = requested_pci_ids
>> +        return self.pci_ids
>> +
>> +
>> +    def catch_vf_devs(self):
>> +        """
>> +        Catch all VFs PCI IDs and return as a list.
>> +        """
>> +        if not self.sr_iov_setup():
>> +            return []
>> +
>> +        cmd = "lspci | awk '/Virtual Function/ {print $1}'"
>> +        return commands.getoutput(cmd).split()
>> +
>> +
>> +    def catch_pf_devs(self):
>> +        """
>> +        Catch all PFs PCI IDs.
>> +        """
>> +        pf_ids = []
>> +        for name in self.name_list:
>> +            pf_id = self._get_pci_id(name, "Ethernet")
>> +            if not pf_id:
>> +                continue
>> +            pf_ids.append(pf_id)
>> +        return pf_ids
>> +
>> +
>> +    def catch_devs(self, count):
>> +        """
>> +        Check out all devices' PCI IDs according to their name.
>> +
>> +        @param count: count number of PCI devices needed for pass through
>> +        @return: a list of all devices' PCI IDs
>> +        """
>> +        if self.type == "nic_vf":
>> +            vf_ids = self.catch_vf_devs()
>> +        elif self.type == "nic_pf":
>> +            vf_ids = self.catch_pf_devs()
>> +        elif self.type == "mixed":
>> +            vf_ids = self.catch_vf_devs()
>> +            vf_ids.extend(self.catch_pf_devs())
>> +        return vf_ids[0:count]
>> diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
>> index 82f1eb4..a0b01fe 100755
>> --- a/client/tests/kvm/kvm_vm.py
>> +++ b/client/tests/kvm/kvm_vm.py
>> @@ -298,6 +298,30 @@ class VM:
>>          elif params.get("uuid"):
>>              qemu_cmd += " -uuid %s" % params.get("uuid")
>>
>> +        # Check whether pci_passthrough is turned on
>> +        if not params.get("pass_through") == "no":
>> +            pt_type = params.get("pass_through")
>> +
>> +            # Virtual Functions pass through
>> +            if params.get("pass_through") == "nic_vf":
>> +                pt = kvm_utils.PassThrough(type=pt_type,
>> +                                           max_vfs=params.get("max_vfs"))
>> +            # Physical NIC card pass through
>> +            elif params.get("pass_through") == "nic_pf":
>> +                pt = kvm_utils.PassThrough(type=pt_type,
>> +                                           names=params.get("passthrough_devs"))
>> +            elif params.get("pass_through") == "mixed":
>> +                pt = kvm_utils.PassThrough(type=pt_type,
>> +                                           max_vfs=params.get("max_vfs"),
>> +                                           names=params.get("passthrough_devs"))
>> +            pt_count = params.get("passthrough_count")
>> +            pt_db_file = open("/tmp/passthrough_db", "w")
>> +            self.pt_pci_ids = pt.request_devs(pt_count, pt_file=pt_db_file,
>> +                                              setup=False)
>> +            pt_db_file.close()
>> +            for pci_id in self.pt_pci_ids:
>> +                qemu_cmd += " -pcidevice host=%s" % pci_id
>> +
>>          return qemu_cmd
>>
>>
>> @@ -380,6 +404,37 @@ class VM:
>>                  self.uuid = f.read().strip()
>>                  f.close()
>>
>> +            # Request specified PCI devices for assignment
>> +            if not params.get("pass_through") == "no":
>> +                pt_type = params.get("pass_through")
>> +
>> +                # Virtual Functions pass through
>> +                if params.get("pass_through") == "nic_vf":
>> +                    logging.debug("Going to assign VFs to guest")
>> +                    pt = kvm_utils.PassThrough(type=pt_type,
>> +                                               max_vfs = params.get("max_vfs"))
>> +                # Physical NIC card pass through
>> +                elif params.get("pass_through") == "nic_pf":
>> +                    logging.debug("Going to assign physical NICs to guest")
>> +                    pt = kvm_utils.PassThrough(type=pt_type,
>> +                                           names=params.get("passthrough_devs"))
>> +                elif params.get("pass_through") == "mixed":
>> +                    logging.debug("Assigned NICs may contain both VFs and PFs")
>> +                    pt = kvm_utils.PassThrough(type = pt_type,
>> +                                               max_vfs = params.get("max_vfs"),
>> +                                         names = params.get("passthrough_devs"))
>> +                pt_count = params.get("passthrough_count")
>> +                pt_db_file = open('/tmp/passthrough_db', 'w')
>> +                self.pt_pci_ids = pt.request_devs(pt_count, pt_file=pt_db_file,
>> +                                                  setup=True)
>> +                pt_db_file.close()
>> +                if not self.pt_pci_ids:
>> +                    logging.error("Request specified PCI device(s) failed;"
>> +                                  " pt_pci_ids are: %s", self.pt_pci_ids)
>> +                    return False
>> +                logging.debug("Requested host PCI device(s): %s",
>> +                              self.pt_pci_ids)
>> +
>>              # Make qemu command
>>              qemu_command = self.make_qemu_command()
>>
>> @@ -504,6 +559,19 @@ class VM:
>>          return (0, data)
>>
>>
>> +    def _release_pt_devs(self):
>> +        """
>> +        Release the assigned passthrough PCI devices or VFs to host.
>> +        """
>> +        try:
>> +            db_file = open('/tmp/passthrough_db', 'r')
>> +            pt_db = cPickle.load(db_file)
>> +            kvm_utils.release_pci_devs(pt_db)
>> +            db_file.close()
>> +        except:
>> +            return
>> +
>> +
>>      def destroy(self, gracefully=True):
>>          """
>>          Destroy the VM.
>> @@ -520,6 +588,7 @@ class VM:
>>              # Is it already dead?
>>              if self.is_dead():
>>                  logging.debug("VM is already down")
>> +                self._release_pt_devs()
>>                  return
>>
>>              logging.debug("Destroying VM with PID %d..." %
>> @@ -540,6 +609,7 @@ class VM:
>>                              return
>>                      finally:
>>                          session.close()
>> +                        self._release_pt_devs()
>>
>>              # Try to destroy with a monitor command
>>              logging.debug("Trying to kill VM with monitor command...")
>> @@ -549,6 +619,7 @@ class VM:
>>                  # Wait for the VM to be really dead
>>                  if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
>>                      logging.debug("VM is down")
>> +                    self._release_pt_devs()
>>                      return
>>
>>              # If the VM isn't dead yet...
>> @@ -558,6 +629,7 @@ class VM:
>>              # Wait for the VM to be really dead
>>              if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
>>                  logging.debug("VM is down")
>> +                self._release_pt_devs()
>>                  return
>>
>>              logging.error("Process %s is a zombie!" % self.process.get_pid())
>> --
>> 1.6.2.5
> _______________________________________________
> Autotest mailing list
> Autotest@xxxxxxxxxxxxxxx
> http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
>



-- 
Lucas
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux