dpdk_nic_bind.py revision 539de1c6
1#! /usr/bin/python
2#
3#   BSD LICENSE
4#
5#   Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
6#   All rights reserved.
7#
8#   Redistribution and use in source and binary forms, with or without
9#   modification, are permitted provided that the following conditions
10#   are met:
11#
12#     * Redistributions of source code must retain the above copyright
13#       notice, this list of conditions and the following disclaimer.
14#     * Redistributions in binary form must reproduce the above copyright
15#       notice, this list of conditions and the following disclaimer in
16#       the documentation and/or other materials provided with the
17#       distribution.
18#     * Neither the name of Intel Corporation nor the names of its
19#       contributors may be used to endorse or promote products derived
20#       from this software without specific prior written permission.
21#
22#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33#
34
35import sys, os, getopt, subprocess, shlex
36from os.path import exists, abspath, dirname, basename
37from distutils.util import strtobool
38text_tables_path = os.path.join('external_libs', 'texttable-0.8.4')
39sys.path.append(text_tables_path)
40import texttable
41sys.path.remove(text_tables_path)
42import re
43import termios
44
45# The PCI device class for ETHERNET devices
46ETHERNET_CLASS = "0200"
47PATH = os.getenv('PATH', '')
48needed_path = '.:/bin:/usr/bin:/usr/sbin'
49if needed_path not in PATH:
50    os.environ['PATH'] = '%s:%s' % (PATH, needed_path)
51
52# global dict ethernet devices present. Dictionary indexed by PCI address.
53# Each device within this is itself a dictionary of device properties
54devices = {}
55# list of supported DPDK drivers
56# ,
57
58dpdk_and_kernel=[ "mlx5_core", "mlx5_ib" ]
59
60dpdk_drivers = ["igb_uio", "vfio-pci", "uio_pci_generic" ]
61
62# command-line arg flags
63b_flag = None
64status_flag = False
65table_flag = False
66force_flag = False
67args = []
68
69try:
70    raw_input
71except: # Python3
72    raw_input = input
73
74def usage():
75    '''Print usage information for the program'''
76    argv0 = basename(sys.argv[0])
77    print("""
78Usage:
79------
80
81     %(argv0)s [options] DEVICE1 DEVICE2 ....
82
83where DEVICE1, DEVICE2 etc, are specified via PCI "domain:bus:slot.func" syntax
84or "bus:slot.func" syntax. For devices bound to Linux kernel drivers, they may
85also be referred to by Linux interface name e.g. eth0, eth1, em0, em1, etc.
86
87Options:
88    --help, --usage:
89        Display usage information and quit
90
91    -s, --status:
92        Print the current status of all known network interfaces.
93        For each device, it displays the PCI domain, bus, slot and function,
94        along with a text description of the device. Depending upon whether the
95        device is being used by a kernel driver, the igb_uio driver, or no
96        driver, other relevant information will be displayed:
97        * the Linux interface name e.g. if=eth0
98        * the driver being used e.g. drv=igb_uio
99        * any suitable drivers not currently using that device
100            e.g. unused=igb_uio
101        NOTE: if this flag is passed along with a bind/unbind option, the status
102        display will always occur after the other operations have taken place.
103
104    -t, --table:
105        Similar to --status, but gives more info: NUMA, MAC etc.
106
107    -b driver, --bind=driver:
108        Select the driver to use or \"none\" to unbind the device
109
110    -u, --unbind:
111        Unbind a device (Equivalent to \"-b none\")
112
113    --force:
114        By default, devices which are used by Linux - as indicated by having
115        established connections - cannot be modified. Using the --force
116        flag overrides this behavior, allowing active links to be forcibly
117        unbound.
118        WARNING: This can lead to loss of network connection and should be used
119        with caution.
120
121Examples:
122---------
123
124To display current device status:
125        %(argv0)s --status
126
127To bind eth1 from the current driver and move to use igb_uio
128        %(argv0)s --bind=igb_uio eth1
129
130To unbind 0000:01:00.0 from using any driver
131        %(argv0)s -u 0000:01:00.0
132
133To bind 0000:02:00.0 and 0000:02:00.1 to the ixgbe kernel driver
134        %(argv0)s -b ixgbe 02:00.0 02:00.1
135
136    """ % locals()) # replace items from local variables
137
138# This is roughly compatible with check_output function in subprocess module
139# which is only available in python 2.7.
140def check_output(args, stderr=None, **kwargs):
141    '''Run a command and capture its output'''
142    return subprocess.Popen(args, stdout=subprocess.PIPE,
143                            stderr=stderr, **kwargs).communicate()[0]
144
145def find_module(mod):
146    '''find the .ko file for kernel module named mod.
147    Searches the $RTE_SDK/$RTE_TARGET directory, the kernel
148    modules directory and finally under the parent directory of
149    the script '''
150    # check $RTE_SDK/$RTE_TARGET directory
151    if 'RTE_SDK' in os.environ and 'RTE_TARGET' in os.environ:
152        path = "%s/%s/kmod/%s.ko" % (os.environ['RTE_SDK'],\
153                                     os.environ['RTE_TARGET'], mod)
154        if exists(path):
155            return path
156
157    # check using depmod
158    try:
159        depmod_out = check_output(["modinfo", "-n", mod], \
160                                  stderr=subprocess.STDOUT, universal_newlines = True).lower()
161        if "error" not in depmod_out:
162            path = depmod_out.strip()
163            if exists(path):
164                return path
165    except: # if modinfo can't find module, it fails, so continue
166        pass
167
168    # check for a copy based off current path
169    tools_dir = dirname(abspath(sys.argv[0]))
170    if (tools_dir.endswith("tools")):
171        base_dir = dirname(tools_dir)
172        find_out = check_output(["find", base_dir, "-name", mod + ".ko"], universal_newlines = True)
173        if len(find_out) > 0: #something matched
174            path = find_out.splitlines()[0]
175            if exists(path):
176                return path
177
178def check_modules():
179    '''Checks that igb_uio is loaded'''
180    global dpdk_drivers
181
182    with open("/proc/modules") as fd:
183        loaded_mods = fd.readlines()
184
185    # list of supported modules
186    mods =  [{"Name" : driver, "Found" : False} for driver in dpdk_drivers]
187
188    # first check if module is loaded
189    for line in loaded_mods:
190        for mod in mods:
191            if line.startswith(mod["Name"]):
192                mod["Found"] = True
193            # special case for vfio_pci (module is named vfio-pci,
194            # but its .ko is named vfio_pci)
195            elif line.replace("_", "-").startswith(mod["Name"]):
196                mod["Found"] = True
197
198    # check if we have at least one loaded module
199    if True not in [mod["Found"] for mod in mods] and b_flag is not None:
200        if b_flag in dpdk_drivers:
201            print("Error - no supported modules(DPDK driver) are loaded")
202            sys.exit(1)
203        else:
204            print("Warning - no supported modules(DPDK driver) are loaded")
205
206    # change DPDK driver list to only contain drivers that are loaded
207    dpdk_drivers = [mod["Name"] for mod in mods if mod["Found"]]
208
209def has_driver(dev_id):
210    '''return true if a device is assigned to a driver. False otherwise'''
211    return "Driver_str" in devices[dev_id]
212
213def get_pci_device_details(dev_id):
214    '''This function gets additional details for a PCI device'''
215    device = {}
216
217    extra_info = check_output(["lspci", "-vmmks", dev_id], universal_newlines = True).splitlines()
218
219    # parse lspci details
220    for line in extra_info:
221        if len(line) == 0:
222            continue
223        name, value = line.split("\t", 1)
224        name = name.strip(":") + "_str"
225        device[name] = value
226    # check for a unix interface name
227    sys_path = "/sys/bus/pci/devices/%s/net/" % dev_id
228    if exists(sys_path):
229        device["Interface"] = ",".join(os.listdir(sys_path))
230        device['net_path'] = sys_path
231    else:
232        device["Interface"] = ""
233        device['net_path'] = None
234        for path, dirs, _ in os.walk('/sys/bus/pci/devices/%s' % dev_id):
235            if 'net' in dirs:
236                device['net_path'] = os.path.join(path, 'net')
237                device["Interface"] = ",".join(os.listdir(device['net_path']))
238                break
239    # check if a port is used for connections (ssh etc)
240    device["Active"] = ""
241
242    return device
243
244def get_nic_details():
245    '''This function populates the "devices" dictionary. The keys used are
246    the pci addresses (domain:bus:slot.func). The values are themselves
247    dictionaries - one for each NIC.'''
248    global devices
249    global dpdk_drivers
250
251    # clear any old data
252    devices = {}
253    # first loop through and read details for all devices
254    # request machine readable format, with numeric IDs
255    dev = {};
256    dev_lines = check_output(["lspci", "-Dvmmn"], universal_newlines = True).splitlines()
257    for dev_line in dev_lines:
258        if (len(dev_line) == 0):
259            if dev["Class"] == ETHERNET_CLASS:
260                #convert device and vendor ids to numbers, then add to global
261                dev["Vendor"] = int(dev["Vendor"],16)
262                dev["Device"] = int(dev["Device"],16)
263                devices[dev["Slot"]] = dict(dev) # use dict to make copy of dev
264        else:
265            name, value = dev_line.split("\t", 1)
266            dev[name.rstrip(":")] = value
267
268    # check active interfaces with established connections
269    established_ip = set()
270    for line in check_output(['netstat', '-tn'], universal_newlines = True).splitlines(): # tcp        0      0 10.56.216.133:22        10.56.46.20:45079       ESTABLISHED
271        line_arr = line.split()
272        if len(line_arr) == 6 and line_arr[0] == 'tcp' and line_arr[5] == 'ESTABLISHED':
273            established_ip.add(line_arr[3].rsplit(':', 1)[0])
274
275    active_if = []
276    for line in check_output(["ip", "-o", "addr"], universal_newlines = True).splitlines(): # 6: eth4    inet 10.56.216.133/24 brd 10.56.216.255 scope global eth4\       valid_lft forever preferred_lft forever
277        line_arr = line.split()
278        if len(line_arr) > 6 and line_arr[2] == 'inet':
279            if line_arr[3].rsplit('/', 1)[0] in established_ip:
280                active_if.append(line_arr[1])
281
282    # based on the basic info, get extended text details
283    for d in devices.keys():
284        # get additional info and add it to existing data
285        devices[d] = dict(list(devices[d].items()) +
286                          list(get_pci_device_details(d).items()))
287
288        for _if in active_if:
289            if _if in devices[d]["Interface"].split(","):
290                devices[d]["Active"] = "*Active*"
291                break;
292
293        # add igb_uio to list of supporting modules if needed
294        if "Module_str" in devices[d]:
295            for driver in dpdk_drivers:
296                if driver not in devices[d]["Module_str"]:
297                    devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver
298        else:
299            devices[d]["Module_str"] = ",".join(dpdk_drivers)
300
301        # make sure the driver and module strings do not have any duplicates
302        if has_driver(d):
303            modules = devices[d]["Module_str"].split(",")
304            if devices[d]["Driver_str"] in modules:
305                modules.remove(devices[d]["Driver_str"])
306                devices[d]["Module_str"] = ",".join(modules)
307
308        # get MAC from Linux if available
309        if devices[d]['net_path']:
310            mac_file = '%s/%s/address' % (devices[d]['net_path'], devices[d]['Interface'])
311            if os.path.exists(mac_file):
312                with open(mac_file) as f:
313                    devices[d]['MAC'] = f.read().strip()
314
315        # get NUMA from Linux if available
316        numa_node_file = '/sys/bus/pci/devices/%s/numa_node' % devices[d]['Slot']
317        if os.path.exists(numa_node_file):
318            with open(numa_node_file) as f:
319                devices[d]['NUMA'] = int(f.read().strip())
320
321def dev_id_from_dev_name(dev_name):
322    '''Take a device "name" - a string passed in by user to identify a NIC
323    device, and determine the device id - i.e. the domain:bus:slot.func - for
324    it, which can then be used to index into the devices array'''
325    dev = None
326    # check if it's already a suitable index
327    if dev_name in devices:
328        return dev_name
329    # check if it's an index just missing the domain part
330    elif "0000:" + dev_name in devices:
331        return "0000:" + dev_name
332    else:
333        # check if it's an interface name, e.g. eth1
334        for d in devices.keys():
335            if dev_name in devices[d]["Interface"].split(","):
336                return devices[d]["Slot"]
337    # if nothing else matches - error
338    print("Unknown device: %s. " \
339        "Please specify device in \"bus:slot.func\" format" % dev_name)
340    sys.exit(1)
341
342def get_igb_uio_usage():
343    for driver in dpdk_drivers:
344        refcnt_file = '/sys/module/%s/refcnt' % driver
345        if not os.path.exists(refcnt_file):
346            continue
347        with open(refcnt_file) as f:
348            ref_cnt = int(f.read().strip())
349            if ref_cnt:
350                return True
351    return False
352
353def get_pid_using_pci(pci_list):
354    if not isinstance(pci_list, list):
355        pci_list = [pci_list]
356    pci_list = list(map(dev_id_from_dev_name, pci_list))
357    for pid in os.listdir('/proc'):
358        try:
359            int(pid)
360        except ValueError:
361            continue
362        with open('/proc/%s/maps' % pid) as f:
363            f_cont = f.read()
364        for pci in pci_list:
365            if '/%s/' % pci in f_cont:
366                return int(pid)
367
368def read_pid_cmdline(pid):
369    cmdline_file = '/proc/%s/cmdline' % pid
370    if not os.path.exists(cmdline_file):
371        return None
372    with open(cmdline_file, 'rb') as f:
373        return f.read().replace(b'\0', b' ').decode(errors = 'replace')
374
375def confirm(msg, default = False):
376    if not os.isatty(1):
377        return default
378    termios.tcflush(sys.stdin, termios.TCIFLUSH)
379    try:
380        return strtobool(raw_input(msg))
381    except KeyboardInterrupt:
382        print('')
383        sys.exit(1)
384    except Exception:
385        return default
386
387def read_line(msg = '', default = ''):
388    if not os.isatty(1):
389        return default
390    termios.tcflush(sys.stdin, termios.TCIFLUSH)
391    try:
392        return raw_input(msg).strip()
393    except KeyboardInterrupt:
394        print('')
395        sys.exit(1)
396
397def unbind_one(dev_id, force):
398    '''Unbind the device identified by "dev_id" from its current driver'''
399    dev = devices[dev_id]
400    if not has_driver(dev_id):
401        print("%s %s %s is not currently managed by any driver\n" % \
402            (dev[b"Slot"], dev[b"Device_str"], dev[b"Interface"]))
403        return
404
405    if not force and dev.get('Driver_str') in dpdk_drivers and get_igb_uio_usage():
406        pid = get_pid_using_pci(dev_id)
407        if pid:
408            cmdline = read_pid_cmdline(pid)
409            print('Interface %s is in use by process:\n%s' % (dev_id, cmdline))
410            if not confirm('Unbinding might hang the process. Confirm unbind (y/N)'):
411                print('Not unbinding.')
412                return
413
414    # prevent us disconnecting ourselves
415    if dev["Active"] and not force:
416        print('netstat indicates that interface %s is active.' % dev_id)
417        if not confirm('Confirm unbind (y/N)'):
418            print('Not unbinding.')
419            return
420
421    # write to /sys to unbind
422    filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
423    try:
424        f = open(filename, "a")
425    except:
426        print("Error: unbind failed for %s - Cannot open %s" % (dev_id, filename))
427        sys.exit(1)
428    f.write(dev_id)
429    f.close()
430
431def bind_one(dev_id, driver, force):
432    '''Bind the device given by "dev_id" to the driver "driver". If the device
433    is already bound to a different driver, it will be unbound first'''
434    dev = devices[dev_id]
435    saved_driver = None # used to rollback any unbind in case of failure
436
437    # prevent disconnection of our ssh session
438    if dev["Active"] and not force:
439        print("netstat indicates that interface %s is active" % dev_id)
440        if not confirm("Confirm bind (y/N)"):
441            print('Not binding.')
442            return
443
444    # unbind any existing drivers we don't want
445    if has_driver(dev_id):
446        if dev["Driver_str"] == driver:
447            print("%s already bound to driver %s, skipping\n" % (dev_id, driver))
448            return
449        else:
450            saved_driver = dev["Driver_str"]
451            if not force and get_igb_uio_usage():
452                pid = get_pid_using_pci(dev_id)
453                if pid:
454                    cmdline = read_pid_cmdline(pid)
455                    print('Interface %s is in use by process:\n%s' % (dev_id, cmdline))
456                    if not confirm('Binding to other driver might hang the process. Confirm unbind (y/N)'):
457                        print('Not binding.')
458                        return
459
460            unbind_one(dev_id, force)
461            dev["Driver_str"] = "" # clear driver string
462
463    # if we are binding to one of DPDK drivers, add PCI id's to that driver
464    if driver in dpdk_drivers:
465        filename = "/sys/bus/pci/drivers/%s/new_id" % driver
466        try:
467            f = open(filename, "w")
468        except:
469            print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename))
470            return
471        try:
472            f.write("%04x %04x" % (dev["Vendor"], dev["Device"]))
473            f.close()
474        except:
475            print("Error: bind failed for %s - Cannot write new PCI ID to " \
476                "driver %s" % (dev_id, driver))
477            return
478
479    # do the bind by writing to /sys
480    filename = "/sys/bus/pci/drivers/%s/bind" % driver
481    try:
482        f = open(filename, "a")
483    except:
484        print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename))
485        if saved_driver is not None: # restore any previous driver
486            bind_one(dev_id, saved_driver, force)
487        return
488    try:
489        f.write(dev_id)
490        f.close()
491    except:
492        # for some reason, closing dev_id after adding a new PCI ID to new_id
493        # results in IOError. however, if the device was successfully bound,
494        # we don't care for any errors and can safely ignore IOError
495        tmp = get_pci_device_details(dev_id)
496        if "Driver_str" in tmp and tmp["Driver_str"] == driver:
497            return
498        print("Error: bind failed for %s - Cannot bind to driver %s" % (dev_id, driver))
499        if saved_driver is not None: # restore any previous driver
500            bind_one(dev_id, saved_driver, force)
501        return
502
503
504def unbind_all(dev_list, force=False):
505    """Unbind method, takes a list of device locations"""
506    dev_list = map(dev_id_from_dev_name, dev_list)
507    for d in dev_list:
508        unbind_one(d, force)
509
510def bind_all(dev_list, driver, force=False):
511    """Bind method, takes a list of device locations"""
512    global devices
513
514    dev_list = list(map(dev_id_from_dev_name, dev_list))
515
516    for d in dev_list:
517        bind_one(d, driver, force)
518
519    # when binding devices to a generic driver (i.e. one that doesn't have a
520    # PCI ID table), some devices that are not bound to any other driver could
521    # be bound even if no one has asked them to. hence, we check the list of
522    # drivers again, and see if some of the previously-unbound devices were
523    # erroneously bound.
524    for d in devices.keys():
525        # skip devices that were already bound or that we know should be bound
526        if "Driver_str" in devices[d] or d in dev_list:
527            continue
528
529        # update information about this device
530        devices[d] = dict(list(devices[d].items()) +
531                          list(get_pci_device_details(d).items()))
532
533        # check if updated information indicates that the device was bound
534        if "Driver_str" in devices[d]:
535            unbind_one(d, force)
536
537def display_devices(title, dev_list, extra_params = None):
538    '''Displays to the user the details of a list of devices given in "dev_list"
539    The "extra_params" parameter, if given, should contain a string with
540    %()s fields in it for replacement by the named fields in each device's
541    dictionary.'''
542    strings = [] # this holds the strings to print. We sort before printing
543    print("\n%s" % title)
544    print("="*len(title))
545    if len(dev_list) == 0:
546        strings.append("<none>")
547    else:
548        for dev in dev_list:
549            if extra_params is not None:
550                strings.append("%s '%s' %s" % (dev["Slot"], \
551                                dev["Device_str"], extra_params % dev))
552            else:
553                strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
554    # sort before printing, so that the entries appear in PCI order
555    strings.sort()
556    print("\n".join(strings)) # print one per line
557
558def show_status():
559    '''Function called when the script is passed the "--status" option. Displays
560    to the user what devices are bound to the igb_uio driver, the kernel driver
561    or to no driver'''
562    global dpdk_drivers
563    if not devices:
564        get_nic_details()
565    kernel_drv = []
566    dpdk_drv = []
567    no_drv = []
568
569    # split our list of devices into the three categories above
570    for d in devices.keys():
571        if not has_driver(d):
572            no_drv.append(devices[d])
573            continue
574
575        if devices[d]["Driver_str"] in dpdk_and_kernel:
576            dpdk_drv.append(devices[d])
577            kernel_drv.append(devices[d])
578        else:
579            if devices[d]["Driver_str"] in dpdk_drivers:
580                dpdk_drv.append(devices[d])
581            else:
582                kernel_drv.append(devices[d])
583
584    # print each category separately, so we can clearly see what's used by DPDK
585    display_devices("Network devices using DPDK-compatible driver", dpdk_drv, \
586                    "drv=%(Driver_str)s unused=%(Module_str)s")
587    display_devices("Network devices using kernel driver", kernel_drv,
588                    "if=%(Interface)s drv=%(Driver_str)s unused=%(Module_str)s %(Active)s")
589    display_devices("Other network devices", no_drv,\
590                    "unused=%(Module_str)s")
591
592def get_info_from_trex(pci_addr_list):
593    if not pci_addr_list:
594        return {}
595    pci_info_dict = {}
596    run_command = 'sudo ./t-rex-64 --dump-interfaces %s' % ' '.join(pci_addr_list)
597    proc = subprocess.Popen(shlex.split(run_command), stdout=subprocess.PIPE,
598                            stderr=subprocess.STDOUT, universal_newlines = True)
599    stdout, _ = proc.communicate()
600    if proc.returncode:
601        if 'PANIC in rte_eal_init' in stdout:
602            print("Could not run TRex to get info about interfaces, check if it's already running.")
603        else:
604            print('Error upon running TRex to get interfaces info:\n%s' % stdout)
605        sys.exit(1)
606    pci_mac_str = 'PCI: (\S+).+?MAC: (\S+).+?Driver: (\S+)'
607    pci_mac_re = re.compile(pci_mac_str)
608    for line in stdout.splitlines():
609        match = pci_mac_re.match(line)
610        if match:
611            pci = match.group(1)
612            if pci not in pci_addr_list: # sanity check, should not happen
613                print('Internal error while getting info of DPDK bound interfaces, unknown PCI: %s' % pci)
614                sys.exit(1)
615            pci_info_dict[pci] = {}
616            pci_info_dict[pci]['MAC'] = match.group(2)
617            pci_info_dict[pci]['TRex_Driver'] = match.group(3)
618    return pci_info_dict
619
620def show_table(get_macs = True):
621    '''Function called when the script is passed the "--table" option.
622    Similar to show_status() function, but shows more info: NUMA etc.'''
623    global dpdk_drivers
624    if not devices:
625        get_nic_details()
626    dpdk_drv = []
627    for d in devices.keys():
628        if devices[d].get("Driver_str") in (dpdk_drivers+dpdk_and_kernel):
629            dpdk_drv.append(d)
630
631    if get_macs:
632        for pci, info in get_info_from_trex(dpdk_drv).items():
633            if pci not in dpdk_drv: # sanity check, should not happen
634                print('Internal error while getting MACs of DPDK bound interfaces, unknown PCI: %s' % pci)
635                return
636            devices[pci].update(info)
637
638    table = texttable.Texttable(max_width=-1)
639    table.header(['ID', 'NUMA', 'PCI', 'MAC', 'Name', 'Driver', 'Linux IF', 'Active'])
640    for id, pci in enumerate(sorted(devices.keys())):
641        d = devices[pci]
642        table.add_row([id, d['NUMA'], d['Slot_str'], d.get('MAC', ''), d['Device_str'], d.get('Driver_str', ''), d['Interface'], d['Active']])
643    print(table.draw())
644
645# dumps pci address and description (user friendly device name)
646def dump_pci_description():
647    if not devices:
648        get_nic_details()
649    for d in devices.values():
650        print('%s - %s' % (d['Slot'], d['Device_str']))
651
652def parse_args():
653    '''Parses the command-line arguments given by the user and takes the
654    appropriate action for each'''
655    global b_flag
656    global status_flag
657    global table_flag
658    global force_flag
659    global args
660    if len(sys.argv) <= 1:
661        usage()
662        sys.exit(0)
663
664    try:
665        opts, args = getopt.getopt(sys.argv[1:], "b:ust",
666                               ["help", "usage", "status", "table", "force",
667                                "bind=", "unbind"])
668    except getopt.GetoptError as error:
669        print(str(error))
670        print("Run '%s --usage' for further information" % sys.argv[0])
671        sys.exit(1)
672
673    for opt, arg in opts:
674        if opt == "--help" or opt == "--usage":
675            usage()
676            sys.exit(0)
677        if opt == "--status" or opt == "-s":
678            status_flag = True
679        if opt == "--table" or opt == "-t":
680            table_flag = True
681        if opt == "--force":
682            force_flag = True
683        if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
684            if b_flag is not None:
685                print("Error - Only one bind or unbind may be specified\n")
686                sys.exit(1)
687            if opt == "-u" or opt == "--unbind":
688                b_flag = "none"
689            else:
690                b_flag = arg
691
692def do_arg_actions():
693    '''do the actual action requested by the user'''
694    global b_flag
695    global status_flag
696    global table_flag
697    global force_flag
698    global args
699
700    if b_flag is None and not status_flag and not table_flag:
701        print("Error: No action specified for devices. Please give a -b or -u option")
702        print("Run '%s --usage' for further information" % sys.argv[0])
703        sys.exit(1)
704
705    if b_flag is not None and len(args) == 0:
706        print("Error: No devices specified.")
707        print("Run '%s --usage' for further information" % sys.argv[0])
708        sys.exit(1)
709
710    if b_flag == "none" or b_flag == "None":
711        unbind_all(args, force_flag)
712    elif b_flag is not None:
713        bind_all(args, b_flag, force_flag)
714    if status_flag:
715        if b_flag is not None:
716            get_nic_details() # refresh if we have changed anything
717        show_status()
718    if table_flag:
719        if b_flag is not None:
720            get_nic_details() # refresh if we have changed anything
721        show_table()
722
723def main():
724    '''program main function'''
725    parse_args()
726    check_modules()
727    get_nic_details()
728    do_arg_actions()
729
730if __name__ == "__main__":
731    if os.getuid() != 0:
732        print('Please run this program as root/with sudo')
733        exit(1)
734    main()
735