1import collections 2import logging 3import os.path 4 5 6PortId = collections.namedtuple('PortId', ['bus', 'port_number']) 7 8GPIO_PATH = '/sys/class/gpio' 9GUADO_CONTROLLER = 'INT3437:00' 10 11# Mapping from bus ID and port number to the GPIO index. 12_PORT_ID_TO_GPIO_INDEX_DICT = { 13 # On Guado, there are three gpios that control usb port power. 14 # These are offsets used to calculate GPIO index. 15 'guado': { 16 # Front ports 17 PortId(bus=1, port_number=2): 56, # Front left USB 2 18 PortId(bus=2, port_number=1): 56, # Front left USB 3 19 PortId(bus=1, port_number=3): 57, # Front right USB 2 20 PortId(bus=2, port_number=2): 57, # Front right USB 3 21 # Back ports (same GPIO is used for both ports) 22 PortId(bus=1, port_number=5): 47, # Back upper USB 2 23 PortId(bus=2, port_number=3): 47, # Back upper USB 3 24 PortId(bus=1, port_number=6): 47, # Back lower USB 2 25 PortId(bus=2, port_number=4): 47, # Back lower USB 3 26 }, 27 'guado-cfm': { 28 # Front ports 29 PortId(bus=1, port_number=2): 56, # Front left USB 2 30 PortId(bus=2, port_number=1): 56, # Front left USB 3 31 PortId(bus=1, port_number=3): 57, # Front right USB 2 32 PortId(bus=2, port_number=2): 57, # Front right USB 3 33 # Back ports (same GPIO is used for both ports) 34 PortId(bus=1, port_number=5): 47, # Back upper USB 2 35 PortId(bus=2, port_number=3): 47, # Back upper USB 3 36 PortId(bus=1, port_number=6): 47, # Back lower USB 2 37 PortId(bus=2, port_number=4): 47, # Back lower USB 3 38 }, 39 # On Fizz, there are in total 5 usb ports and per port usb power 40 # is controlled by EC with user space command: 41 # ectool gpioset USBx_ENABLE 0/1 (x from 1 to 5). 42 'fizz': { 43 # USB 2 bus. 44 PortId(bus=1, port_number=3): 4, # Front right USB 2 45 PortId(bus=1, port_number=4): 5, # Front left USB 2 46 PortId(bus=1, port_number=5): 1, # Back left USB 2 47 PortId(bus=1, port_number=6): 2, # Back middle USB 2 48 PortId(bus=1, port_number=2): 3, # Back right USB 2 49 # USB 3 bus. 50 PortId(bus=2, port_number=3): 4, # Front right USB 3 51 PortId(bus=2, port_number=4): 5, # Front left USB 3 52 PortId(bus=2, port_number=5): 1, # Back left USB 3 53 PortId(bus=2, port_number=6): 2, # Back middle USB 3 54 PortId(bus=2, port_number=2): 3, # Back right USB 3 55 }, 56 'fizz-cfm': { 57 # USB 2 bus. 58 PortId(bus=1, port_number=3): 4, # Front right USB 2 59 PortId(bus=1, port_number=4): 5, # Front left USB 2 60 PortId(bus=1, port_number=5): 1, # Back left USB 2 61 PortId(bus=1, port_number=6): 2, # Back middle USB 2 62 PortId(bus=1, port_number=2): 3, # Back right USB 2 63 # USB 3 bus. 64 PortId(bus=2, port_number=3): 4, # Front right USB 3 65 PortId(bus=2, port_number=4): 5, # Front left USB 3 66 PortId(bus=2, port_number=5): 1, # Back left USB 3 67 PortId(bus=2, port_number=6): 2, # Back middle USB 3 68 PortId(bus=2, port_number=2): 3, # Back right USB 3 69 } 70} 71 72 73def _get_gpio_index(board, port_id): 74 return _PORT_ID_TO_GPIO_INDEX_DICT[board][port_id] 75 76 77class UsbPortManager(object): 78 """ 79 Manages USB ports. 80 81 Can for example power cycle them. 82 """ 83 def __init__(self, host): 84 """ 85 Initializes with a host. 86 87 @param host a Host object. 88 """ 89 self._host = host 90 91 def set_port_power(self, port_ids, power_on): 92 """ 93 Turns on or off power to the USB port for peripheral devices. 94 95 @param port_ids Iterable of PortId instances (i.e. bus, port_number 96 tuples) to set power for. 97 @param power_on If true, turns power on. If false, turns power off. 98 """ 99 for port_id in port_ids: 100 gpio_index = _get_gpio_index(self._get_board(), port_id) 101 self._set_gpio_power(self._get_board(), gpio_index, power_on) 102 103 def _find_gpio_base_index(self, expected_controller): 104 """ 105 Finds the gpiochip* base index using the expected controller. 106 107 If `cat /sys/class/gpio/gpiochip<N>/label` has the expected controller, return <N> 108 109 @param expected_controller The controller to match to return gpiochip<N>/base 110 """ 111 gpiochips = self._run( 112 'ls -d /sys/class/gpio/gpiochip*').stdout.strip().split('\n') 113 if not gpiochips: 114 raise ValueError('No gpiochips found') 115 116 for gpiochip in gpiochips: 117 logging.debug('Checking gpiochip path "%s" for controller %s', 118 gpiochip, expected_controller) 119 gpiochip_label = os.path.join(gpiochip, 'label') 120 gpiochip_controller = self._run( 121 'cat {}'.format(gpiochip_label)).stdout.strip() 122 123 if gpiochip_controller == expected_controller: 124 gpiochip_base = os.path.join(gpiochip, 'base') 125 gpiochip_base_index = self._run( 126 'cat {}'.format(gpiochip_base)).stdout.strip() 127 return int(gpiochip_base_index) 128 129 raise ValueError('Expected controller not found') 130 131 def _get_board(self): 132 # host.get_board() adds 'board: ' in front of the board name 133 return self._host.get_board().split(':')[1].strip() 134 135 def _set_gpio_power_guado(self, gpio_idx, power_on): 136 """ 137 Turns on or off the power for a specific GPIO on board Guado. 138 139 @param gpio_idx The *offset* of the gpio to set the power for, added to the base. 140 @param power_on If True, powers on the GPIO. If False, powers it off. 141 """ 142 143 # First, we need to find the gpio base 144 gpio_base_index = self._find_gpio_base_index(GUADO_CONTROLLER) 145 146 # Once base is found, calculate index 147 gpio_index = gpio_base_index + gpio_idx 148 logging.debug('Using gpio index: "%s"', gpio_index) 149 150 gpio_path = '/sys/class/gpio/gpio{}'.format(gpio_index) 151 did_export = False 152 if not self._host.path_exists(gpio_path): 153 did_export = True 154 self._run('echo {} > /sys/class/gpio/export'.format( 155 gpio_index)) 156 try: 157 self._run('echo out > {}/direction'.format(gpio_path)) 158 value_string = '1' if power_on else '0' 159 self._run('echo {} > {}/value'.format( 160 value_string, gpio_path)) 161 finally: 162 if did_export: 163 self._run('echo {} > /sys/class/gpio/unexport'.format( 164 gpio_index)) 165 166 def _set_gpio_power_fizz(self, gpio_idx, power_on): 167 """ 168 Turns on or off the power for a specific GPIO on board Fizz. 169 170 @param gpio_idx The index of the gpio to set the power for. 171 @param power_on If True, powers on the GPIO. If False, powers it off. 172 """ 173 value_string = '1' if power_on else '0' 174 cmd = 'ectool gpioset USB{}_ENABLE {}'.format(gpio_idx, 175 value_string) 176 self._run(cmd) 177 178 def _set_gpio_power(self, board, gpio_index, power_on): 179 """ 180 Turns on or off the power for a specific GPIO. 181 182 @param board Board type. Currently support: Guado, Fizz. 183 @param gpio_idx The index of the gpio to set the power for. 184 @param power_on If True, powers on the GPIO. If False, powers it off. 185 """ 186 if board == 'guado' or board == 'guado-cfm': 187 self._set_gpio_power_guado(gpio_index, power_on) 188 elif board == 'fizz' or board == 'fizz-cfm': 189 self._set_gpio_power_fizz(gpio_index, power_on) 190 else: 191 raise ValueError('Unsupported board type {}.'.format(board)) 192 193 def _run(self, command): 194 logging.debug('Running: "%s"', command) 195 res = self._host.run(command) 196 logging.debug('Result: "%s"', res) 197 return res 198