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