Home | History | Annotate | Download | only in network
      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 
      5 import logging
      6 import os
      7 
      8 from autotest_lib.client.common_lib import error
      9 
     10 
     11 SYS_GPIO_PATH = '/sys/class/gpio/'
     12 SYS_PINMUX_PATH = '/sys/kernel/debug/omap_mux/'
     13 OMAP_MUX_GPIO_MODE = 'OMAP_MUX_MODE7'
     14 
     15 MAX_VARIABLE_ATTENUATION = 95
     16 
     17 # Index of GPIO banks. Each GPIO bank is 32-bit long.
     18 GPIO_BANK0 = 0
     19 GPIO_BANK1 = 1
     20 GPIO_BANK2 = 2
     21 
     22 
     23 class 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
     72 VARIABLE_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.
    115 HOST_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 
    179 class 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