Merge "hugepages: change default num pages + deallocate"
[vswitchperf.git] / tools / networkcard.py
1 # Copyright 2016 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Tools for network card manipulation
16 """
17
18 import os
19 import subprocess
20 import logging
21 import glob
22 from conf import settings
23
24 _LOGGER = logging.getLogger('tools.networkcard')
25
26 _PCI_DIR = '/sys/bus/pci/devices/{}/'
27 _SRIOV_NUMVFS = os.path.join(_PCI_DIR, 'sriov_numvfs')
28 _SRIOV_TOTALVFS = os.path.join(_PCI_DIR, 'sriov_totalvfs')
29 _SRIOV_VF_PREFIX = 'virtfn'
30 _SRIOV_PF = 'physfn'
31 _PCI_NET = 'net'
32 _PCI_DRIVER = 'driver'
33
34
35 def check_pci(pci_handle):
36     """ Checks if given extended PCI handle has correct length and fixes
37         it if possible.
38
39     :param pci_handle: PCI slot identifier. It can contain vsperf specific
40         suffix after '|' with VF indication. e.g. '0000:05:00.0|vf1'
41
42     :returns: PCI handle
43     """
44     pci = pci_handle.split('|')
45     pci_len = len(pci[0])
46     if pci_len == 12:
47         return pci_handle
48     elif pci_len == 7:
49         pci[0] = '0000:' + pci[0][-7:]
50         _LOGGER.debug('Adding domain part to PCI slot %s', pci[0])
51         return '|'.join(pci)
52     elif pci_len > 12:
53         pci[0] = pci[0][-12:]
54         _LOGGER.warning('PCI slot is too long, it will be shortened to %s', pci[0])
55         return '|'.join(pci)
56     else:
57         # pci_handle has a strange length, but let us try to use it
58         _LOGGER.error('Unknown format of PCI slot %s', pci_handle)
59         return pci_handle
60
61 def is_sriov_supported(pci_handle):
62     """ Checks if sriov is supported by given NIC
63
64     :param pci_handle: PCI slot identifier with domain part.
65
66     :returns: True on success, False otherwise
67     """
68     return os.path.isfile(_SRIOV_TOTALVFS.format(pci_handle))
69
70 def is_sriov_nic(pci_handle):
71     """ Checks if given extended PCI ID refers to the VF
72
73     :param pci_handle: PCI slot identifier with domain part. It can contain
74         vsperf specific suffix after '|' with VF indication.
75         e.g. '0000:05:00.0|vf1'
76
77     :returns: True on success, False otherwise
78     """
79     for item in pci_handle.split('|'):
80         if item.lower().startswith('vf'):
81             return True
82     return False
83
84 def set_sriov_numvfs(pci_handle, numvfs):
85     """ Checks if sriov is supported and configures given number of VFs
86
87     :param pci_handle: PCI slot identifier with domain part.
88     :param numvfs: Number of VFs to be configured at given NIC.
89
90     :returns: True on success, False otherwise
91     """
92     if not is_sriov_supported(pci_handle):
93         return False
94
95     if get_sriov_numvfs(pci_handle) == numvfs:
96         return True
97
98     if numvfs and get_sriov_numvfs(pci_handle) != 0:
99         if not set_sriov_numvfs(pci_handle, 0):
100             return False
101
102     try:
103         subprocess.call('sudo bash -c "echo {} > {}"'.format(numvfs, _SRIOV_NUMVFS.format(pci_handle)), shell=True)
104         return get_sriov_numvfs(pci_handle) == numvfs
105     except OSError:
106         _LOGGER.debug('Number of VFs cant be changed to %s for PF %s', numvfs, pci_handle)
107         return False
108
109 def get_sriov_numvfs(pci_handle):
110     """ Returns the number of configured VFs
111
112     :param pci_handle: PCI slot identifier with domain part
113     :returns: the number of configured VFs
114     """
115     if is_sriov_supported(pci_handle):
116         with open(_SRIOV_NUMVFS.format(pci_handle), 'r') as numvfs:
117             return int(numvfs.readline().rstrip('\n'))
118
119     return None
120
121 def get_sriov_totalvfs(pci_handle):
122     """ Checks if sriov is supported and returns max number of supported VFs
123
124     :param pci_handle: PCI slot identifier with domain part
125     :returns: the max number of supported VFs by given NIC
126     """
127     if is_sriov_supported(pci_handle):
128         with open(_SRIOV_TOTALVFS.format(pci_handle), 'r') as total:
129             return int(total.readline().rstrip('\n'))
130
131     return None
132
133 def get_sriov_vfs_list(pf_pci_handle):
134     """ Returns list of PCI handles of VFs configured at given NIC/PF
135
136     :param pf_pci_handle: PCI slot identifier of PF with domain part.
137     :returns: list
138     """
139     vfs = []
140     if is_sriov_supported(pf_pci_handle):
141         for vf_name in glob.glob(os.path.join(_PCI_DIR, _SRIOV_VF_PREFIX + '*').format(pf_pci_handle)):
142             vfs.append(os.path.basename(os.path.realpath(vf_name)))
143
144     return vfs
145
146 def get_sriov_pf(vf_pci_handle):
147     """ Get PCI handle of PF which belongs to given VF
148
149     :param vf_pci_handle: PCI slot identifier of VF with domain part.
150     :returns: PCI handle of parent PF
151     """
152     pf_path = os.path.join(_PCI_DIR, _SRIOV_PF).format(vf_pci_handle)
153     if os.path.isdir(pf_path):
154         return os.path.basename(os.path.realpath(pf_path))
155
156     return None
157
158 def get_driver(pci_handle):
159     """ Returns name of kernel driver assigned to given NIC
160
161     :param pci_handle: PCI slot identifier with domain part.
162     :returns: string with assigned kernel driver, None otherwise
163     """
164     driver_path = os.path.join(_PCI_DIR, _PCI_DRIVER).format(pci_handle)
165     if os.path.isdir(driver_path):
166         return os.path.basename(os.path.realpath(driver_path))
167
168     return None
169
170 def get_device_name(pci_handle):
171     """ Returns name of network card device name
172
173     :param pci_handle: PCI slot identifier with domain part.
174     :returns: string with assigned NIC device name, None otherwise
175     """
176     net_path = os.path.join(_PCI_DIR, _PCI_NET).format(pci_handle)
177     try:
178         return os.listdir(net_path)[0]
179     except FileNotFoundError:
180         return None
181     except IndexError:
182         return None
183
184     return None
185
186 def get_mac(pci_handle):
187     """ Returns MAC address of given NIC
188
189     :param pci_handle: PCI slot identifier with domain part.
190     :returns: string with assigned MAC address, None otherwise
191     """
192     mac_path = glob.glob(os.path.join(_PCI_DIR, _PCI_NET, '*', 'address').format(pci_handle))
193     # kernel driver is loaded and MAC can be read
194     if len(mac_path) and os.path.isfile(mac_path[0]):
195         with open(mac_path[0], 'r') as _file:
196             return _file.readline().rstrip('\n')
197
198     # MAC address is unknown, e.g. NIC is assigned to DPDK
199     return None
200
201 def get_nic_info(full_pci_handle):
202     """ Parse given pci handle with additional info and returns
203         requested NIC info.
204
205     :param full_pci_handle: A string with extended network card PCI ID.
206         extended PCI ID syntax: PCI_ID[|vfx][|(mac|dev)]
207         examples:
208             0000:06:00.0            - returns the same value
209             0000:06:00.0|vf0        - returns PCI ID of 1st virtual function of given NIC
210             0000:06:00.0|mac        - returns MAC address of given NIC
211             0000:06:00.0|vf0|mac    - returns MAC address of 1st virtual function of given NIC
212
213     :returns: A string with requested NIC data or None if data cannot be read.
214     """
215     parsed_handle = full_pci_handle.split('|')
216     if len(parsed_handle) not in (1, 2, 3):
217         _LOGGER.error("Invalid PCI device name: '%s'", full_pci_handle)
218         return None
219
220     pci_handle = parsed_handle[0]
221
222     for action in parsed_handle[1:]:
223         # in case of SRIOV get PCI handle of given virtual function
224         if action.lower().startswith('vf'):
225             try:
226                 vf_num = int(action[2:])
227                 pci_handle = get_sriov_vfs_list(pci_handle)[vf_num]
228             except ValueError:
229                 _LOGGER.error("Pci device '%s', does not have VF with index '%s'", pci_handle, action[2:])
230                 return None
231             except IndexError:
232                 _LOGGER.error("Pci device '%s', does not have VF with index '%s'", pci_handle, vf_num)
233                 return None
234             continue
235
236         # return requested info for given PCI handle
237         if action.lower() == 'mac':
238             return get_mac(pci_handle)
239         elif action.lower() == 'dev':
240             return get_device_name(pci_handle)
241         else:
242             _LOGGER.error("Invalid item '%s' in PCI handle '%s'", action, full_pci_handle)
243             return None
244
245     return pci_handle
246
247 def reinit_vfs(pf_pci_handle):
248     """ Reinitializates all VFs, which belong to given PF
249
250     :param pf_pci_handle: PCI slot identifier of PF with domain part.
251     """
252     rte_pci_tool = glob.glob(os.path.join(settings.getValue('RTE_SDK'), 'tools', 'dpdk*bind.py'))[0]
253
254     for vf_nic in get_sriov_vfs_list(pf_pci_handle):
255         nic_driver = get_driver(vf_nic)
256         if nic_driver:
257             try:
258                 subprocess.call(['sudo', rte_pci_tool, '--unbind', vf_nic],
259                                 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
260                 subprocess.call(['sudo', rte_pci_tool, '--bind=' + nic_driver, vf_nic],
261                                 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
262             except subprocess.CalledProcessError:
263                 _LOGGER.warning('Error during reinitialization of VF %s', vf_nic)
264         else:
265             _LOGGER.warning("Can't detect driver for VF %s", vf_nic)
266