# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import logging import os from autotest_lib.client.common_lib import error SYS_GPIO_PATH = '/sys/class/gpio/' SYS_PINMUX_PATH = '/sys/kernel/debug/omap_mux/' OMAP_MUX_GPIO_MODE = 'OMAP_MUX_MODE7' MAX_VARIABLE_ATTENUATION = 95 # Index of GPIO banks. Each GPIO bank is 32-bit long. GPIO_BANK0 = 0 GPIO_BANK1 = 1 GPIO_BANK2 = 2 class GpioPin(object): """Contains relevant details about a GPIO pin.""" def __init__(self, bank, bit, pinmux_file, pin_name): """Construct a GPIO pin object. @param bank: int GPIO bank number (from 0-2 on BeagleBone White). @param bit: int bit offset in bank (from 0-31 on BeagleBone White). @param pinmux_file: string name of pinmux file. This file is used to set the mode of a pin. For instance, some pins are part of UART interfaces in addition to being GPIO capable. @param pin_name: string name of pin for debugging. """ self.offset = str(bank * 32 + bit) self.pinmux_file = os.path.join(SYS_PINMUX_PATH, pinmux_file) self.pin_name = pin_name self.value_file = os.path.join(SYS_GPIO_PATH, 'gpio' + self.offset, 'value') self.export_file = os.path.join(SYS_GPIO_PATH, 'export') self.unexport_file = os.path.join(SYS_GPIO_PATH, 'unexport') self.direction_file = os.path.join(SYS_GPIO_PATH, 'gpio' + self.offset, 'direction') # Variable attenuators are controlled by turning GPIOs on and off. GPIOs # are arranged in 3 banks on the BeagleBone White, 32 pins to a bank. We # pick groups of 8 pins such that the pins are physically near to each other # to form the inputs to a given variable attenuator. These inputs spell # a binary word, which corresponds to the generated attenuation in dB. For # instance, turning on bits 0, 3, and 5 in a group: # # attenuation = (1 << 0) + (1 << 3) + (1 << 5) = 0x25 = 37 dB # # Bits are listed in ascending order in a group (bit 0 first). There are # four groups of bits, one group per attenuator. # # Note that there is also a fixed amount of loss generated by the attenuator # that we account for in the constant for the fixed loss along the path # for a given antenna. # # On hosts with 4 attenuators, these are arranged so that attenuators 0/1 # control the main/aux antennas of a radio, and 2/3 control the main/aux # lines of a second radio. For hosts with only two attenuators, there # should also be only a single phy. # # These mappings are specific to: # hardware: BeagleBone board (revision A3) # operating system: Angstrom Linux v2011.11-core (Core edition) # image version: # Angstrom-Cloud9-IDE-eglibc-ipk-v2011.11-core-beaglebone-2011.11.16 VARIABLE_ATTENUATORS = { 0: [GpioPin(GPIO_BANK1, 31, 'gpmc_csn2', 'GPIO1_31'), GpioPin(GPIO_BANK1, 30, 'gpmc_csn1', 'GPIO1_30'), GpioPin(GPIO_BANK1, 5, 'gpmc_ad5', 'GPIO1_5'), GpioPin(GPIO_BANK1, 4, 'gpmc_ad4', 'GPIO1_4'), GpioPin(GPIO_BANK1, 1, 'gpmc_ad1', 'GPIO1_1'), GpioPin(GPIO_BANK1, 0, 'gpmc_ad0', 'GPIO1_0'), GpioPin(GPIO_BANK1, 29, 'gpmc_csn0', 'GPIO1_29'), GpioPin(GPIO_BANK2, 22, 'lcd_vsync', 'GPIO2_22'), ], 1: [GpioPin(GPIO_BANK1, 6, 'gpmc_ad6', 'GPIO1_6'), GpioPin(GPIO_BANK1, 2, 'gpmc_ad2', 'GPIO1_2'), GpioPin(GPIO_BANK1, 3, 'gpmc_ad3', 'GPIO1_3'), GpioPin(GPIO_BANK2, 2, 'gpmc_advn_ale', 'TIMER4'), GpioPin(GPIO_BANK2, 3, 'gpmc_oen_ren', 'TIMER7'), GpioPin(GPIO_BANK2, 5, 'gpmc_ben0_cle', 'TIMER5'), GpioPin(GPIO_BANK2, 4, 'gpmc_wen', 'TIMER6'), GpioPin(GPIO_BANK1, 13, 'gpmc_ad13', 'GPIO1_13'), ], 2: [GpioPin(GPIO_BANK1, 12, 'gpmc_ad12', 'GPIO1_12'), GpioPin(GPIO_BANK0, 23, 'gpmc_ad9', 'EHRPWM2B'), GpioPin(GPIO_BANK0, 26, 'gpmc_ad10', 'GPIO0_26'), GpioPin(GPIO_BANK1, 15, 'gpmc_ad15', 'GPIO1_15'), GpioPin(GPIO_BANK1, 14, 'gpmc_ad14', 'GPIO1_14'), GpioPin(GPIO_BANK0, 27, 'gpmc_ad11', 'GPIO0_27'), GpioPin(GPIO_BANK2, 1, 'mcasp0_fsr', 'GPIO2_1'), GpioPin(GPIO_BANK0, 22, 'gpmc_ad11', 'EHRPWM2A'), ], 3: [GpioPin(GPIO_BANK2, 24, 'lcd_pclk', 'GPIO2_24'), GpioPin(GPIO_BANK2, 23, 'lcd_hsync', 'GPIO2_23'), GpioPin(GPIO_BANK2, 25, 'lcd_ac_bias_en', 'GPIO2_25'), GpioPin(GPIO_BANK0, 10, 'lcd_data14', 'UART5_CTSN'), GpioPin(GPIO_BANK0, 11, 'lcd_data15', 'UART5_RTSN'), GpioPin(GPIO_BANK0, 9, 'lcd_data13', 'UART4_RTSN'), GpioPin(GPIO_BANK2, 17, 'lcd_data11', 'UART3_RTSN'), GpioPin(GPIO_BANK0, 8, 'lcd_data12', 'UART4_CTSN'), ], } # This map represents the fixed loss overhead on a given antenna line. # The map maps from: # attenuator hostname -> attenuator number -> frequency -> loss in dB. HOST_TO_FIXED_ATTENUATIONS = { 'chromeos1-grover-host1-attenuator': { 0: {2437: 53, 5220: 56, 5765: 56}, 1: {2437: 54, 5220: 56, 5765: 59}, 2: {2437: 54, 5220: 57, 5765: 57}, 3: {2437: 54, 5220: 57, 5765: 59}}, 'chromeos1-grover-host2-attenuator': { 0: {2437: 55, 5220: 59, 5765: 59}, 1: {2437: 53, 5220: 55, 5765: 55}, 2: {2437: 56, 5220: 60, 5765: 59}, 3: {2437: 56, 5220: 58, 5765: 58}}, 'chromeos1-grover-host3-attenuator': { 0: {2437: 54, 5220: 59, 5765: 57}, 1: {2437: 54, 5220: 57, 5765: 57}, 2: {2437: 54, 5220: 58, 5765: 57}, 3: {2437: 54, 5220: 57, 5765: 57}}, 'chromeos1-grover-host4-attenuator': { 0: {2437: 54, 5220: 58, 5765: 58}, 1: {2437: 54, 5220: 58, 5765: 58}, 2: {2437: 54, 5220: 58, 5765: 58}, 3: {2437: 54, 5220: 57, 5765: 57}}, 'chromeos1-grover-host5-attenuator': { 0: {2437: 51, 5220: 59, 5765: 64}, 1: {2437: 52, 5220: 56, 5765: 57}, 2: {2437: 53, 5220: 57, 5765: 61}, 3: {2437: 52, 5220: 56, 5765: 57}}, 'chromeos1-grover-host6-attenuator': { 0: {2437: 54, 5220: 56, 5765: 57}, 1: {2437: 54, 5220: 56, 5765: 58}, 2: {2437: 54, 5220: 56, 5765: 57}, 3: {2437: 54, 5220: 57, 5765: 58}}, 'chromeos1-grover-host7-attenuator': { 0: {2437: 59, 5220: 61, 5765: 62}, 1: {2437: 59, 5220: 64, 5765: 66}, 2: {2437: 59, 5220: 61, 5765: 65}, 3: {2437: 58, 5220: 60, 5765: 63}}, 'chromeos1-grover-host8-attenuator': { 0: {2437: 64, 5220: 64, 5765: 63}, 1: {2437: 65, 5220: 61, 5765: 63}, 2: {2437: 66, 5220: 67, 5765: 70}, 3: {2437: 68, 5220: 64, 5765: 65}}, 'chromeos1-grover-host9-attenuator': { 0: {2437: 56, 5220: 63, 5765: 64}, 1: {2437: 59, 5220: 63, 5765: 66}, 2: {2437: 59, 5220: 65, 5765: 66}, 3: {2437: 57, 5220: 63, 5765: 63}}, 'chromeos1-grover-host10-attenuator': { 0: {2437: 59, 5220: 64, 5765: 67}, 1: {2437: 66, 5220: 70, 5765: 64}, 2: {2437: 60, 5220: 67, 5765: 65}, 3: {2437: 65, 5220: 68, 5765: 61}}, 'chromeos1-grover-host11-attenuator': { 0: {2437: 62, 5220: 62, 5765: 66}, 1: {2437: 57, 5220: 63, 5765: 65}, 2: {2437: 63, 5220: 63, 5765: 68}, 3: {2437: 56, 5220: 60, 5765: 64}}, 'chromeos1-grover-host12-attenuator': { 0: {2437: 68, 5220: 66, 5765: 70}, 1: {2437: 56, 5220: 60, 5765: 63}, 2: {2437: 67, 5220: 64, 5765: 68}, 3: {2437: 57, 5220: 61, 5765: 64}}, } class AttenuatorController(object): """Represents a BeagleBone controlling several variable attenuators. This device is used to vary the attenuation between a router and a client. This allows us to measure throughput as a function of signal strength and test some roaming situations. The throughput vs signal strength tests are referred to rate vs range (RvR) tests in places. @see BeagleBone System Reference Manual (RevA3_1.0): http://beagleboard.org/static/beaglebone/a3/Docs/Hardware/BONE_SRM.pdf @see Texas Instrument's GPIO Driver Guide http://processors.wiki.ti.com/index.php/GPIO_Driver_Guide """ @property def supported_attenuators(self): """@return iterable of int attenuators supported on this host.""" return self._fixed_attenuations.keys() def __init__(self, host): """Construct a AttenuatorController. @param host: Host object representing the remote BeagleBone. """ super(AttenuatorController, self).__init__() self._host = host hostname = host.hostname if hostname.find('.') > 0: hostname = hostname[0:hostname.find('.')] if hostname not in HOST_TO_FIXED_ATTENUATIONS.keys(): raise error.TestError('Unexpected RvR host name %r.' % hostname) self._fixed_attenuations = HOST_TO_FIXED_ATTENUATIONS[hostname] logging.info('Configuring GPIO ports on attenuator host.') for attenuator in self.supported_attenuators: for gpio_pin in VARIABLE_ATTENUATORS[attenuator]: self._enable_gpio_pin(gpio_pin) self._setup_gpio_pin(gpio_pin) self.set_variable_attenuation(0) def _approximate_frequency(self, attenuator_num, freq): """Finds an approximate frequency to freq. In case freq is not present in self._fixed_attenuations, we use a value from a nearby channel as an approximation. @param attenuator_num: attenuator in question on the remote host. Each attenuator has a different fixed path loss per frequency. @param freq: int frequency in MHz. @returns int approximate frequency from self._fixed_attenuations. """ old_offset = None approx_freq = None for defined_freq in self._fixed_attenuations[attenuator_num].keys(): new_offset = abs(defined_freq - freq) if old_offset is None or new_offset < old_offset: old_offset = new_offset approx_freq = defined_freq logging.debug('Approximating attenuation for frequency %d with ' 'constants for frequency %d.', freq, approx_freq) return approx_freq def _enable_gpio_pin(self, gpio_pin): """Enable a pin's GPIO function. @param gpio_pin: GpioPin object. """ self._host.run('echo 7 > %s' % gpio_pin.pinmux_file) # Example contents of pinmux sysfile: # name: lcd_pclk.lcd_pclk (0x44e108e8/0x8e8 = 0x0000), b NA, t NA # mode: OMAP_PIN_OUTPUT | OMAP_MUX_MODE0 # signals: lcd_pclk | NA | NA | NA | NA | NA | NA | NA desired_prefix = 'mode:' result = self._host.run('cat %s' % gpio_pin.pinmux_file) for line in result.stdout.splitlines(): if not line.startswith(desired_prefix): continue line = line[len(desired_prefix):] modes = [mode.strip() for mode in line.split('|')] break else: raise error.TestError('Failed to parse pinmux file') if OMAP_MUX_GPIO_MODE not in modes: raise error.TestError('Error setting pin %s to GPIO mode' % gpio_pin.pin_name) def _setup_gpio_pin(self, gpio_pin, enable=True): """Export or unexport a GPIO pin. GPIO pins must be exported before becoming usable. @param gpio_pin: GpioPin object. @param enable: bool True to export this pin. """ if enable: sysfile = gpio_pin.export_file else: sysfile = gpio_pin.unexport_file self._host.run('echo %s > %s' % (gpio_pin.offset, sysfile), ignore_status=True) if enable: # Set it to output self._host.run('echo out > %s' % gpio_pin.direction_file) def close(self): """Close this BB host and turn off all variabel attenuation.""" self.set_variable_attenuation(0) self._host.close() def set_total_attenuation(self, atten_db, frequency_mhz, attenuator_num=None): """Set the total attenuation on one or all attenuators. @param atten_db: int level of attenuation in dB. This must be higher than the fixed attenuation level of the affected attenuators. @param frequency_mhz: int frequency for which to calculate the total attenuation. The fixed component of attenuation varies with frequency. @param attenuator_num: int attenuator to change, or None to set all variable attenuators. """ affected_attenuators = self.supported_attenuators if attenuator_num is not None: affected_attenuators = [attenuator_num] for attenuator in affected_attenuators: freq_to_fixed_loss = self._fixed_attenuations[attenuator] approx_freq = self._approximate_frequency(attenuator, frequency_mhz) variable_atten_db = atten_db - freq_to_fixed_loss[approx_freq] self.set_variable_attenuation(variable_atten_db, attenuator_num=attenuator) def set_variable_attenuation(self, atten_db, attenuator_num=None): """Set the variable attenuation on one or all attenuators. @param atten_db: int non-negative level of attenuation in dB. @param attenuator_num: int attenuator to change, or None to set all variable attenuators. """ if atten_db > MAX_VARIABLE_ATTENUATION: raise error.TestError('Requested variable attenuation greater ' 'than maximum. (%d > %d)' % (atten_db, MAX_VARIABLE_ATTENUATION)) if atten_db < 0: raise error.TestError('Only positive attenuations are supported. ' '(requested %d)' % atten_db) affected_attenuators = self.supported_attenuators if attenuator_num is not None: affected_attenuators = [attenuator_num] for attenuator in affected_attenuators: bit_field = atten_db for gpio_pin in VARIABLE_ATTENUATORS[attenuator]: bit_value = bit_field & 1 self._host.run('echo %d > %s' % (bit_value, gpio_pin.value_file)) bit_field = bit_field >> 1 def get_minimal_total_attenuation(self): """Get attenuator's maximum fixed attenuation value. This is pulled from the current attenuator's lines and becomes the minimal total attenuation when stepping through attenuation levels. @return maximum starting attenuation value """ max_atten = 0 for atten_num in self._fixed_attenuations.iterkeys(): atten_values = self._fixed_attenuations[atten_num].values() max_atten = max(max(atten_values), max_atten) return max_atten