• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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