| # 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 |