• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
7
8from autotest_lib.client.common_lib import error
9
10
11SYS_GPIO_PATH = '/sys/class/gpio/'
12SYS_PINMUX_PATH = '/sys/kernel/debug/omap_mux/'
13OMAP_MUX_GPIO_MODE = 'OMAP_MUX_MODE7'
14
15MAX_VARIABLE_ATTENUATION = 95
16
17# Index of GPIO banks. Each GPIO bank is 32-bit long.
18GPIO_BANK0 = 0
19GPIO_BANK1 = 1
20GPIO_BANK2 = 2
21
22
23class GpioPin(object):
24    """Contains relevant details about a GPIO pin."""
25    def __init__(self, bank, bit, pinmux_file, pin_name):
26        """Construct a GPIO pin object.
27
28        @param bank: int GPIO bank number (from 0-2 on BeagleBone White).
29        @param bit: int bit offset in bank (from 0-31 on BeagleBone White).
30        @param pinmux_file: string name of pinmux file.  This file is used to
31                set the mode of a pin.  For instance, some pins are part of
32                UART interfaces in addition to being GPIO capable.
33        @param pin_name: string name of pin for debugging.
34
35        """
36        self.offset = str(bank * 32 + bit)
37        self.pinmux_file = os.path.join(SYS_PINMUX_PATH, pinmux_file)
38        self.pin_name = pin_name
39        self.value_file = os.path.join(SYS_GPIO_PATH, 'gpio' + self.offset,
40                                       'value')
41        self.export_file = os.path.join(SYS_GPIO_PATH, 'export')
42        self.unexport_file = os.path.join(SYS_GPIO_PATH, 'unexport')
43        self.direction_file = os.path.join(SYS_GPIO_PATH, 'gpio' + self.offset,
44                                           'direction')
45
46# Variable attenuators are controlled by turning GPIOs on and off.  GPIOs
47# are arranged in 3 banks on the BeagleBone White, 32 pins to a bank.  We
48# pick groups of 8 pins such that the pins are physically near to each other
49# to form the inputs to a given variable attenuator.  These inputs spell
50# a binary word, which corresponds to the generated attenuation in dB.  For
51# instance, turning on bits 0, 3, and 5 in a group:
52#
53#      attenuation = (1 << 0) + (1 << 3) + (1 << 5) = 0x25 = 37 dB
54#
55# Bits are listed in ascending order in a group (bit 0 first).  There are
56# four groups of bits, one group per attenuator.
57#
58# Note that there is also a fixed amount of loss generated by the attenuator
59# that we account for in the constant for the fixed loss along the path
60# for a given antenna.
61#
62# On hosts with 4 attenuators, these are arranged so that attenuators 0/1
63# control the main/aux antennas of a radio, and 2/3 control the main/aux
64# lines of a second radio.  For hosts with only two attenuators, there
65# should also be only a single phy.
66#
67# These mappings are specific to:
68#  hardware: BeagleBone board (revision A3)
69#  operating system: Angstrom Linux v2011.11-core (Core edition)
70#  image version:
71#      Angstrom-Cloud9-IDE-eglibc-ipk-v2011.11-core-beaglebone-2011.11.16
72VARIABLE_ATTENUATORS = {
73        0: [GpioPin(GPIO_BANK1, 31, 'gpmc_csn2', 'GPIO1_31'),
74            GpioPin(GPIO_BANK1, 30, 'gpmc_csn1', 'GPIO1_30'),
75            GpioPin(GPIO_BANK1, 5,  'gpmc_ad5',  'GPIO1_5'),
76            GpioPin(GPIO_BANK1, 4,  'gpmc_ad4',  'GPIO1_4'),
77            GpioPin(GPIO_BANK1, 1,  'gpmc_ad1',  'GPIO1_1'),
78            GpioPin(GPIO_BANK1, 0,  'gpmc_ad0',  'GPIO1_0'),
79            GpioPin(GPIO_BANK1, 29, 'gpmc_csn0', 'GPIO1_29'),
80            GpioPin(GPIO_BANK2, 22, 'lcd_vsync', 'GPIO2_22'),
81           ],
82        1: [GpioPin(GPIO_BANK1, 6,  'gpmc_ad6',      'GPIO1_6'),
83            GpioPin(GPIO_BANK1, 2,  'gpmc_ad2',      'GPIO1_2'),
84            GpioPin(GPIO_BANK1, 3,  'gpmc_ad3',      'GPIO1_3'),
85            GpioPin(GPIO_BANK2, 2,  'gpmc_advn_ale', 'TIMER4'),
86            GpioPin(GPIO_BANK2, 3,  'gpmc_oen_ren',  'TIMER7'),
87            GpioPin(GPIO_BANK2, 5,  'gpmc_ben0_cle', 'TIMER5'),
88            GpioPin(GPIO_BANK2, 4,  'gpmc_wen',      'TIMER6'),
89            GpioPin(GPIO_BANK1, 13, 'gpmc_ad13',     'GPIO1_13'),
90           ],
91        2: [GpioPin(GPIO_BANK1, 12, 'gpmc_ad12',  'GPIO1_12'),
92            GpioPin(GPIO_BANK0, 23, 'gpmc_ad9',   'EHRPWM2B'),
93            GpioPin(GPIO_BANK0, 26, 'gpmc_ad10',  'GPIO0_26'),
94            GpioPin(GPIO_BANK1, 15, 'gpmc_ad15',  'GPIO1_15'),
95            GpioPin(GPIO_BANK1, 14, 'gpmc_ad14',  'GPIO1_14'),
96            GpioPin(GPIO_BANK0, 27, 'gpmc_ad11',  'GPIO0_27'),
97            GpioPin(GPIO_BANK2, 1,  'mcasp0_fsr', 'GPIO2_1'),
98            GpioPin(GPIO_BANK0, 22, 'gpmc_ad11',  'EHRPWM2A'),
99           ],
100        3: [GpioPin(GPIO_BANK2, 24, 'lcd_pclk',       'GPIO2_24'),
101            GpioPin(GPIO_BANK2, 23, 'lcd_hsync',      'GPIO2_23'),
102            GpioPin(GPIO_BANK2, 25, 'lcd_ac_bias_en', 'GPIO2_25'),
103            GpioPin(GPIO_BANK0, 10, 'lcd_data14',     'UART5_CTSN'),
104            GpioPin(GPIO_BANK0, 11, 'lcd_data15',     'UART5_RTSN'),
105            GpioPin(GPIO_BANK0, 9,  'lcd_data13',     'UART4_RTSN'),
106            GpioPin(GPIO_BANK2, 17, 'lcd_data11',     'UART3_RTSN'),
107            GpioPin(GPIO_BANK0, 8,  'lcd_data12',     'UART4_CTSN'),
108           ],
109}
110
111
112# This map represents the fixed loss overhead on a given antenna line.
113# The map maps from:
114#     attenuator hostname -> attenuator number -> frequency -> loss in dB.
115HOST_TO_FIXED_ATTENUATIONS = {
116        'chromeos1-grover-host1-attenuator': {
117                0: {2437: 53, 5220: 56, 5765: 56},
118                1: {2437: 54, 5220: 56, 5765: 59},
119                2: {2437: 54, 5220: 57, 5765: 57},
120                3: {2437: 54, 5220: 57, 5765: 59}},
121        'chromeos1-grover-host2-attenuator': {
122                0: {2437: 55, 5220: 59, 5765: 59},
123                1: {2437: 53, 5220: 55, 5765: 55},
124                2: {2437: 56, 5220: 60, 5765: 59},
125                3: {2437: 56, 5220: 58, 5765: 58}},
126        'chromeos1-grover-host3-attenuator': {
127                0: {2437: 54, 5220: 59, 5765: 57},
128                1: {2437: 54, 5220: 57, 5765: 57},
129                2: {2437: 54, 5220: 58, 5765: 57},
130                3: {2437: 54, 5220: 57, 5765: 57}},
131        'chromeos1-grover-host4-attenuator': {
132                0: {2437: 54, 5220: 58, 5765: 58},
133                1: {2437: 54, 5220: 58, 5765: 58},
134                2: {2437: 54, 5220: 58, 5765: 58},
135                3: {2437: 54, 5220: 57, 5765: 57}},
136        'chromeos1-grover-host5-attenuator': {
137                0: {2437: 51, 5220: 59, 5765: 64},
138                1: {2437: 52, 5220: 56, 5765: 57},
139                2: {2437: 53, 5220: 57, 5765: 61},
140                3: {2437: 52, 5220: 56, 5765: 57}},
141        'chromeos1-grover-host6-attenuator': {
142                0: {2437: 54, 5220: 56, 5765: 57},
143                1: {2437: 54, 5220: 56, 5765: 58},
144                2: {2437: 54, 5220: 56, 5765: 57},
145                3: {2437: 54, 5220: 57, 5765: 58}},
146        'chromeos1-grover-host7-attenuator': {
147                0: {2437: 59, 5220: 61, 5765: 62},
148                1: {2437: 59, 5220: 64, 5765: 66},
149                2: {2437: 59, 5220: 61, 5765: 65},
150                3: {2437: 58, 5220: 60, 5765: 63}},
151        'chromeos1-grover-host8-attenuator': {
152                0: {2437: 64, 5220: 64, 5765: 63},
153                1: {2437: 65, 5220: 61, 5765: 63},
154                2: {2437: 66, 5220: 67, 5765: 70},
155                3: {2437: 68, 5220: 64, 5765: 65}},
156        'chromeos1-grover-host9-attenuator': {
157                0: {2437: 56, 5220: 63, 5765: 64},
158                1: {2437: 59, 5220: 63, 5765: 66},
159                2: {2437: 59, 5220: 65, 5765: 66},
160                3: {2437: 57, 5220: 63, 5765: 63}},
161        'chromeos1-grover-host10-attenuator': {
162                0: {2437: 59, 5220: 64, 5765: 67},
163                1: {2437: 66, 5220: 70, 5765: 64},
164                2: {2437: 60, 5220: 67, 5765: 65},
165                3: {2437: 65, 5220: 68, 5765: 61}},
166        'chromeos1-grover-host11-attenuator': {
167                0: {2437: 62, 5220: 62, 5765: 66},
168                1: {2437: 57, 5220: 63, 5765: 65},
169                2: {2437: 63, 5220: 63, 5765: 68},
170                3: {2437: 56, 5220: 60, 5765: 64}},
171        'chromeos1-grover-host12-attenuator': {
172                0: {2437: 68, 5220: 66, 5765: 70},
173                1: {2437: 56, 5220: 60, 5765: 63},
174                2: {2437: 67, 5220: 64, 5765: 68},
175                3: {2437: 57, 5220: 61, 5765: 64}},
176        }
177
178
179class AttenuatorController(object):
180    """Represents a BeagleBone controlling several variable attenuators.
181
182    This device is used to vary the attenuation between a router and a client.
183    This allows us to measure throughput as a function of signal strength and
184    test some roaming situations.  The throughput vs signal strength tests
185    are referred to rate vs range (RvR) tests in places.
186
187    @see BeagleBone System Reference Manual (RevA3_1.0):
188        http://beagleboard.org/static/beaglebone/a3/Docs/Hardware/BONE_SRM.pdf
189    @see Texas Instrument's GPIO Driver Guide
190        http://processors.wiki.ti.com/index.php/GPIO_Driver_Guide
191
192    """
193
194    @property
195    def supported_attenuators(self):
196        """@return iterable of int attenuators supported on this host."""
197        return self._fixed_attenuations.keys()
198
199
200    def __init__(self, host):
201        """Construct a AttenuatorController.
202
203        @param host: Host object representing the remote BeagleBone.
204
205        """
206        super(AttenuatorController, self).__init__()
207        self._host = host
208        hostname = host.hostname
209        if hostname.find('.') > 0:
210            hostname = hostname[0:hostname.find('.')]
211        if hostname not in HOST_TO_FIXED_ATTENUATIONS.keys():
212            raise error.TestError('Unexpected RvR host name %r.' % hostname)
213        self._fixed_attenuations = HOST_TO_FIXED_ATTENUATIONS[hostname]
214        logging.info('Configuring GPIO ports on attenuator host.')
215        for attenuator in self.supported_attenuators:
216            for gpio_pin in VARIABLE_ATTENUATORS[attenuator]:
217                self._enable_gpio_pin(gpio_pin)
218                self._setup_gpio_pin(gpio_pin)
219        self.set_variable_attenuation(0)
220
221
222    def _approximate_frequency(self, attenuator_num, freq):
223        """Finds an approximate frequency to freq.
224
225        In case freq is not present in self._fixed_attenuations, we use a value
226        from a nearby channel as an approximation.
227
228        @param attenuator_num: attenuator in question on the remote host.  Each
229                attenuator has a different fixed path loss per frequency.
230        @param freq: int frequency in MHz.
231        @returns int approximate frequency from self._fixed_attenuations.
232
233        """
234        old_offset = None
235        approx_freq = None
236        for defined_freq in self._fixed_attenuations[attenuator_num].keys():
237            new_offset = abs(defined_freq - freq)
238            if old_offset is None or new_offset < old_offset:
239                old_offset = new_offset
240                approx_freq = defined_freq
241
242        logging.debug('Approximating attenuation for frequency %d with '
243                      'constants for frequency %d.', freq, approx_freq)
244        return approx_freq
245
246
247    def _enable_gpio_pin(self, gpio_pin):
248        """Enable a pin's GPIO function.
249
250        @param gpio_pin: GpioPin object.
251
252        """
253        self._host.run('echo 7 > %s' % gpio_pin.pinmux_file)
254        # Example contents of pinmux sysfile:
255        #  name: lcd_pclk.lcd_pclk (0x44e108e8/0x8e8 = 0x0000), b NA, t NA
256        #  mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE0
257        #  signals: lcd_pclk | NA | NA | NA | NA | NA | NA | NA
258        desired_prefix = 'mode:'
259        result = self._host.run('cat %s' % gpio_pin.pinmux_file)
260        for line in result.stdout.splitlines():
261            if not line.startswith(desired_prefix):
262                continue
263            line = line[len(desired_prefix):]
264            modes = [mode.strip() for mode in line.split('|')]
265            break
266        else:
267            raise error.TestError('Failed to parse pinmux file')
268
269        if OMAP_MUX_GPIO_MODE not in modes:
270            raise error.TestError('Error setting pin %s to GPIO mode' %
271                                  gpio_pin.pin_name)
272
273
274    def _setup_gpio_pin(self, gpio_pin, enable=True):
275        """Export or unexport a GPIO pin.
276
277        GPIO pins must be exported before becoming usable.
278
279        @param gpio_pin: GpioPin object.
280        @param enable: bool True to export this pin.
281
282        """
283        if enable:
284            sysfile = gpio_pin.export_file
285        else:
286            sysfile = gpio_pin.unexport_file
287        self._host.run('echo %s > %s' % (gpio_pin.offset, sysfile),
288                       ignore_status=True)
289        if enable:
290            # Set it to output
291            self._host.run('echo out > %s' % gpio_pin.direction_file)
292
293
294    def close(self):
295        """Close this BB host and turn off all variabel attenuation."""
296        self.set_variable_attenuation(0)
297        self._host.close()
298
299
300    def set_total_attenuation(self, atten_db, frequency_mhz,
301                              attenuator_num=None):
302        """Set the total attenuation on one or all attenuators.
303
304        @param atten_db: int level of attenuation in dB.  This must be
305                higher than the fixed attenuation level of the affected
306                attenuators.
307        @param frequency_mhz: int frequency for which to calculate the
308                total attenuation.  The fixed component of attenuation
309                varies with frequency.
310        @param attenuator_num: int attenuator to change, or None to
311                set all variable attenuators.
312
313        """
314        affected_attenuators = self.supported_attenuators
315        if attenuator_num is not None:
316            affected_attenuators = [attenuator_num]
317        for attenuator in affected_attenuators:
318            freq_to_fixed_loss = self._fixed_attenuations[attenuator]
319            approx_freq = self._approximate_frequency(attenuator,
320                                                      frequency_mhz)
321            variable_atten_db = atten_db - freq_to_fixed_loss[approx_freq]
322            self.set_variable_attenuation(variable_atten_db,
323                                          attenuator_num=attenuator)
324
325
326    def set_variable_attenuation(self, atten_db, attenuator_num=None):
327        """Set the variable attenuation on one or all attenuators.
328
329        @param atten_db: int non-negative level of attenuation in dB.
330        @param attenuator_num: int attenuator to change, or None to
331                set all variable attenuators.
332
333        """
334        if atten_db > MAX_VARIABLE_ATTENUATION:
335            raise error.TestError('Requested variable attenuation greater '
336                                  'than maximum. (%d > %d)' %
337                                  (atten_db, MAX_VARIABLE_ATTENUATION))
338
339        if atten_db < 0:
340            raise error.TestError('Only positive attenuations are supported. '
341                                  '(requested %d)' % atten_db)
342
343        affected_attenuators = self.supported_attenuators
344        if attenuator_num is not None:
345            affected_attenuators = [attenuator_num]
346        for attenuator in affected_attenuators:
347            bit_field = atten_db
348            for gpio_pin in VARIABLE_ATTENUATORS[attenuator]:
349                bit_value = bit_field & 1
350                self._host.run('echo %d > %s' %
351                               (bit_value, gpio_pin.value_file))
352                bit_field = bit_field >> 1
353
354
355    def get_minimal_total_attenuation(self):
356        """Get attenuator's maximum fixed attenuation value.
357
358        This is pulled from the current attenuator's lines and becomes the
359        minimal total attenuation when stepping through attenuation levels.
360
361        @return maximum starting attenuation value
362
363        """
364        max_atten = 0
365        for atten_num in self._fixed_attenuations.iterkeys():
366            atten_values = self._fixed_attenuations[atten_num].values()
367            max_atten = max(max(atten_values), max_atten)
368        return max_atten
369