update copyright date to 2013
[dpdk.git] / tools / pci_unbind.py
1 #! /usr/bin/python
2 #
3 #   BSD LICENSE
4
5 #   Copyright(c) 2010-2013 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 #
35
36 import sys, os, getopt, subprocess
37 from os.path import exists, abspath, dirname, basename
38
39
40 # The PCI device class for ETHERNET devices
41 ETHERNET_CLASS = "0200"
42
43 # global dict ethernet devices present. Dictionary indexed by PCI address.
44 # Each device within this is itself a dictionary of device properties
45 devices = {}
46 # list of vendor:device pairs (again stored as dictionary) supported by igb_uio
47 module_dev_ids = []
48
49 def usage():
50     '''Print usage information for the program'''
51     argv0 = basename(sys.argv[0])
52     print """
53 Usage:
54 ------
55
56      %(argv0)s [options] DEVICE1 DEVICE2 ....
57
58 where DEVICE1, DEVICE2 etc, are specified via PCI "domain:bus:slot.func" syntax
59 or "bus:slot.func" syntax. For devices bound to Linux kernel drivers, they may
60 also be referred to by Linux interface name e.g. eth0, eth1, em0, em1, etc.
61
62 Options:
63     --help, --usage:
64         Display usage information and quit
65         
66     --status:
67         Print the current status of all known network interfaces.
68         For each device, it displays the PCI domain, bus, slot and function,
69         along with a text description of the device. Depending upon whether the
70         device is being used by a kernel driver, the igb_uio driver, or no
71         driver, other relevant information will be displayed:
72         * the Linux interface name e.g. if=eth0
73         * the driver being used e.g. drv=igb_uio
74         * any suitable drivers not currently using that device 
75             e.g. unused=igb_uio 
76         NOTE: if this flag is passed along with a bind/unbind option, the status
77         display will always occur after the other operations have taken place.
78         
79     -b driver, --bind=driver: 
80         Select the driver to use or \"none\" to unbind the device
81         
82     -u, --unbind: 
83         Unbind a device (Equivalent to \"-b none\")
84         
85     --force:
86         By default, devices which are used by Linux - as indicated by having
87         routes in the routing table - cannot be modified. Using the --force
88         flag overrides this behavior, allowing active links to be forcibly
89         unbound. 
90         WARNING: This can lead to loss of network connection and should be used
91         with caution.
92         
93 Examples:
94 ---------
95     
96 To display current device status:
97         %(argv0)s --status
98         
99 To bind eth1 from the current driver and move to use igb_uio
100         %(argv0)s --bind=igb_uio eth1
101         
102 To unbind 0000:01:00.0 from using any driver
103         %(argv0)s -u 0000:01:00.0
104         
105 To bind 0000:02:00.0 and 0000:02:00.1 to the ixgbe kernel driver
106         %(argv0)s -b ixgbe 02:00.0 02:00.1
107     
108     """ % locals() # replace items from local variables
109
110 # This is roughly compatible with check_output function in subprocess module
111 # which is only available in python 2.7. 
112 def check_output(args, stderr=None):
113     '''Run a command and capture its output'''
114     return subprocess.Popen(args, stdout=subprocess.PIPE,
115                             stderr=stderr).communicate()[0]
116
117 def find_module(mod):
118     '''find the .ko file for kernel module named mod.
119     Searches the $RTE_SDK/$RTE_TARGET directory, the kernel
120     modules directory and finally under the parent directory of
121     the script '''
122     # check $RTE_SDK/$RTE_TARGET directory
123     if 'RTE_SDK' in os.environ and 'RTE_TARGET' in os.environ:
124         path = "%s/%s/kmod/%s.ko" % (os.environ['RTE_SDK'],\
125                                      os.environ['RTE_TARGET'], mod)
126         if exists(path):
127             return path
128         
129     # check using depmod
130     try: 
131         depmod_out = check_output(["modinfo", "-n", mod], \
132                                   stderr=subprocess.STDOUT).lower()
133         if "error" not in depmod_out:
134             path = depmod_out.strip()
135             if exists(path):
136                 return path
137     except: # if modinfo can't find module, it fails, so continue
138         pass
139     
140     # check for a copy based off current path
141     tools_dir = dirname(abspath(sys.argv[0]))
142     if (tools_dir.endswith("tools")):
143         base_dir = dirname(tools_dir)
144         find_out = check_output(["find", base_dir, "-name", mod + ".ko"])
145         if len(find_out) > 0: #something matched
146             path = find_out.splitlines()[0]
147             if exists(path):
148                 return path
149
150 def check_modules():
151     '''Checks that the needed modules (igb_uio) is loaded, and then
152     determine from the .ko file, what its supported device ids are'''
153     global module_dev_ids
154     
155     fd = file("/proc/modules")
156     loaded_mods = fd.readlines()
157     fd.close()
158     mod = "igb_uio"
159     
160     # first check if module is loaded
161     found = False
162     for line in loaded_mods:
163         if line.startswith(mod):
164             found = True
165             break
166     if not found:
167         print "Error - module %s not loaded" %mod
168         sys.exit(1)
169     
170     # now find the .ko and get list of supported vendor/dev-ids
171     modpath = find_module(mod)
172     if modpath is None:
173         print "Cannot find module file %s" % (mod + ".ko")
174         sys.exit(1)
175     depmod_output = check_output(["depmod", "-n", modpath]).splitlines()
176     for line in depmod_output:
177         if not line.startswith(mod):
178             continue
179         if line.endswith(mod+".ko:"):
180             continue
181         lineparts = line.split()
182         module_dev_ids.append({"Vendor": int(lineparts[1],0), 
183                                "Device": int(lineparts[2],0)})
184
185 def is_supported_device(dev_id):
186     '''return true if device is supported by igb_uio, false otherwise'''
187     for dev in module_dev_ids:
188         if (dev["Vendor"] == devices[dev_id]["Vendor"] and 
189             dev["Device"] == devices[dev_id]["Device"]):
190             return True
191     return False
192
193 def has_driver(dev_id):
194     '''return true if a device is assigned to a driver. False otherwise'''
195     return "Driver_str" in devices[dev_id]
196
197 def get_nic_details():
198     '''This function populates the "devices" dictionary. The keys used are
199     the pci addresses (domain:bus:slot.func). The values are themselves
200     dictionaries - one for each NIC.'''
201     global devices
202     
203     # clear any old data
204     devices = {} 
205     # first loop through and read details for all devices
206     # request machine readable format, with numeric IDs
207     dev = {};
208     dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
209     for dev_line in dev_lines:
210         if (len(dev_line) == 0):
211             if dev["Class"] == ETHERNET_CLASS:
212                 #convert device and vendor ids to numbers, then add to global
213                 dev["Vendor"] = int(dev["Vendor"],16)
214                 dev["Device"] = int(dev["Device"],16)
215                 devices[dev["Slot"]] = dict(dev) # use dict to make copy of dev
216         else:
217             name, value = dev_line.split("\t", 1)
218             dev[name.rstrip(":")] = value
219
220     # check what is the interface if any for an ssh connection if
221     # any to this host, so we can mark it later.
222     ssh_if = []
223     route = check_output(["ip", "-o", "route"])
224     # filter out all lines for 169.254 routes
225     route = "\n".join(filter(lambda ln: not ln.startswith("169.254"), 
226                              route.splitlines()))
227     rt_info = route.split()
228     for i in xrange(len(rt_info) - 1):
229         if rt_info[i] == "dev":
230             ssh_if.append(rt_info[i+1])
231
232     # based on the basic info, get extended text details            
233     for d in devices.keys():
234         extra_info = check_output(["lspci", "-vmmks", d]).splitlines()
235         # parse lspci details
236         for line in extra_info:
237             if len(line) == 0:
238                 continue
239             name, value = line.split("\t", 1)
240             name = name.strip(":") + "_str"
241             devices[d][name] = value
242         # check for a unix interface name
243         sys_path = "/sys/bus/pci/devices/%s/net/" % d
244         if exists(sys_path):
245             devices[d]["Interface"] = ",".join(os.listdir(sys_path))
246         else:
247             devices[d]["Interface"] = ""
248         # check if a port is used for ssh connection
249         devices[d]["Ssh_if"] = False
250         devices[d]["Active"] = ""
251         for _if in ssh_if: 
252             if _if in devices[d]["Interface"].split(","):
253                 devices[d]["Ssh_if"] = True
254                 devices[d]["Active"] = "*Active*"
255                 break;
256
257         # add igb_uio to list of supporting modules if needed
258         if is_supported_device(d):
259             if "Module_str" in devices[d]:
260                 if "igb_uio" not in devices[d]["Module_str"]:
261                     devices[d]["Module_str"] = devices[d]["Module_str"] + ",igb_uio"
262             else:
263                 devices[d]["Module_str"] = "igb_uio"
264         if "Module_str" not in devices[d]:
265             devices[d]["Module_str"] = "<none>"
266         # make sure the driver and module strings do not have any duplicates
267         if has_driver(d):
268             modules = devices[d]["Module_str"].split(",")
269             if devices[d]["Driver_str"] in modules:
270                 modules.remove(devices[d]["Driver_str"])
271                 devices[d]["Module_str"] = ",".join(modules)
272     
273 def dev_id_from_dev_name(dev_name):
274     '''Take a device "name" - a string passed in by user to identify a NIC
275     device, and determine the device id - i.e. the domain:bus:slot.func - for
276     it, which can then be used to index into the devices array'''
277     dev = None
278     # check if it's already a suitable index
279     if dev_name in devices:
280         return dev_name
281     # check if it's an index just missing the domain part 
282     elif "0000:" + dev_name in devices:
283         return "0000:" + dev_name
284     else:
285         # check if it's an interface name, e.g. eth1
286         for d in devices.keys():
287             if dev_name in devices[d]["Interface"].split(","):
288                 return devices[d]["Slot"]
289     # if nothing else matches - error
290     print "Unknown device: %s. " \
291         "Please specify device in \"bus:slot.func\" format" % dev_name
292     sys.exit(1)
293
294 def unbind_one(dev_id, force):
295     '''Unbind the device identified by "dev_id" from its current driver'''
296     dev = devices[dev_id]
297     if not has_driver(dev_id):
298         print "%s %s %s is not currently managed by any driver\n" % \
299             (dev["Slot"], dev["Device_str"], dev["Interface"])
300         return
301     
302     # prevent us disconnecting ourselves
303     if dev["Ssh_if"] and not force:
304         print "Routing table indicates that interface %s is active" \
305             ". Skipping unbind" % (dev_id)
306         return
307     
308     # write to /sys to unbind
309     filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
310     try:
311         f = open(filename, "a")
312     except:
313         print "Error: unbind failed for %s - Cannot open %s" % (dev_id, filename)
314         sys/exit(1)
315     f.write(dev_id)
316     f.close()
317
318 def bind_one(dev_id, driver, force):
319     '''Bind the device given by "dev_id" to the driver "driver". If the device
320     is already bound to a different driver, it will be unbound first'''
321     dev = devices[dev_id]
322     saved_driver = None # used to rollback any unbind in case of failure
323     
324     # prevent disconnection of our ssh session
325     if dev["Ssh_if"] and not force:
326         print "Routing table indicates that interface %s is active" \
327             ". Not modifying" % (dev_id)
328         return
329
330     # unbind any existing drivers we don't want
331     if has_driver(dev_id):
332         if dev["Driver_str"] == driver:
333             print "%s already bound to driver %s, skipping\n" % (dev_id, driver)
334             return
335         else:
336             saved_driver = dev["Driver_str"]
337             unbind_one(dev_id, force)
338             dev["Driver_str"] = "" # clear driver string
339
340     # do the bind by writing to /sys
341     filename = "/sys/bus/pci/drivers/%s/bind" % driver
342     try:
343         f = open(filename, "a")
344     except:
345         print "Error: bind failed for %s - Cannot open %s" % (dev_id, filename)
346         if saved_driver is not None: # restore any previous driver
347             bind_one(dev_id, saved_driver, force)
348         return
349     try:
350         f.write(dev_id)
351         f.close()
352     except:
353         print "Error: bind failed for %s - Cannot bind to driver %s" % (dev_id, driver)
354         if saved_driver is not None: # restore any previous driver
355             bind_one(dev_id, saved_driver, force)
356         return
357
358
359 def unbind_all(dev_list, force=False):
360     """Unbind method, takes a list of device locations"""
361     dev_list = map(dev_id_from_dev_name, dev_list)
362     for d in dev_list:
363         unbind_one(d, force)
364
365 def bind_all(dev_list, driver, force=False):
366     """Unbind method, takes a list of device locations"""
367     dev_list = map(dev_id_from_dev_name, dev_list)
368     for d in dev_list:
369         bind_one(d, driver, force)
370
371 def display_devices(title, dev_list, extra_params = None):
372     '''Displays to the user the details of a list of devices given in "dev_list"
373     The "extra_params" parameter, if given, should contain a string with
374     %()s fields in it for replacement by the named fields in each device's
375     dictionary.''' 
376     strings = [] # this holds the strings to print. We sort before printing
377     print "\n%s" % title
378     print   "="*len(title)
379     if len(dev_list) == 0:
380         strings.append("<none>")
381     else:
382         for dev in dev_list:
383             if extra_params is not None:
384                 strings.append("%s '%s' %s" % (dev["Slot"], \
385                                 dev["Device_str"], extra_params % dev))
386             else:
387                 strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
388     # sort before printing, so that the entries appear in PCI order
389     strings.sort()
390     print "\n".join(strings) # print one per line
391
392 def show_status():
393     '''Function called when the script is passed the "--status" option. Displays
394     to the user what devices are bound to the igb_uio driver, the kernel driver
395     or to no driver'''
396     kernel_drv = []
397     uio_drv = []
398     no_drv = []
399     # split our list of devices into the three categories above
400     for d in devices.keys():
401         if not has_driver(d):
402             no_drv.append(devices[d])
403             continue
404         if devices[d]["Driver_str"] == "igb_uio":
405             uio_drv.append(devices[d])
406         else:
407             kernel_drv.append(devices[d])
408
409     # print each category separately, so we can clearly see what's used by DPDK
410     display_devices("Network devices using IGB_UIO driver", uio_drv, \
411                     "drv=%(Driver_str)s unused=%(Module_str)s")
412     display_devices("Network devices using kernel driver", kernel_drv,
413                     "if=%(Interface)s drv=%(Driver_str)s unused=%(Module_str)s %(Active)s")
414     display_devices("Other network devices", no_drv,\
415                     "unused=%(Module_str)s")
416
417 def parse_args():
418     '''Parses the command-line arguments given by the user and takes the
419     appropriate action for each'''
420     b_flag = None
421     status_flag = False
422     force_flag = False
423     if len(sys.argv) <= 1:
424         usage()
425         sys.exit(0)
426     
427     try:
428         opts, args = getopt.getopt(sys.argv[1:], "b:u",
429                                ["help", "usage", "status", "force",
430                                 "bind=", "unbind"])
431     except getopt.GetoptError, error:
432         print str(error)
433         print "Run '%s --usage' for further information" % sys.argv[0]
434         sys.exit(1)
435         
436     for opt, arg in opts:
437         if opt == "--help" or opt == "--usage":
438             usage()
439             sys.exit(0)
440         if opt == "--status":
441             status_flag = True
442         if opt == "--force":
443             force_flag = True
444         if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
445             if b_flag is not None:
446                 print "Error - Only one bind or unbind may be specified\n"
447                 sys.exit(1)
448             if opt == "-u" or opt == "--unbind":
449                 b_flag = "none"
450             else:
451                 b_flag = arg
452                 
453     if b_flag is None and not status_flag:
454         print "Error: No action specified for devices. Please give a -b or -u option"
455         print "Run '%s --usage' for further information" % sys.argv[0]
456         sys.exit(1)
457     
458     if b_flag is not None and len(args) == 0:
459         print "Error: No devices specified."
460         print "Run '%s --usage' for further information" % sys.argv[0]
461         sys.exit(1)
462
463     if b_flag == "none" or b_flag == "None":
464         unbind_all(args, force_flag)
465     elif b_flag is not None:
466         bind_all(args, b_flag, force_flag)
467     if status_flag:
468         if b_flag is not None:
469             get_nic_details() # refresh if we have changed anything
470         show_status()
471                         
472 def main():
473     '''program main function'''
474     check_modules()
475     get_nic_details()
476     parse_args()
477
478 if __name__ == "__main__":
479     main()