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