""" # b43 debugging library # # Copyright (C) 2008-2010 Michael Buesch # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ import sys import os import re import hashlib from tempfile import * # SHM routing values B43_SHM_UCODE = 0 B43_SHM_SHARED = 1 B43_SHM_REGS = 2 B43_SHM_IHR = 3 B43_SHM_RCMTA = 4 class B43Exception(Exception): pass B43_MMIO_MACCTL = 0x120 B43_MMIO_PSMDEBUG = 0x154 B43_MACCTL_PSM_MACEN = 0x00000001 B43_MACCTL_PSM_RUN = 0x00000002 B43_MACCTL_PSM_JMP0 = 0x00000004 B43_MACCTL_PSM_DEBUG = 0x00002000 class B43PsmDebug: """Parse the contents of the PSM-debug register""" def __init__(self, reg_content): self.raw = reg_content return def getRaw(self): """Get the raw PSM-debug register value""" return self.raw def getPc(self): """Get the microcode program counter""" return self.raw & 0xFFF class B43: """Hardware access layer. This accesses the hardware through the debugfs interface.""" def __init__(self, phy=None): debugfs_path = self.__debugfs_find() # Construct the debugfs b43 path to the device b43_path = debugfs_path + "/b43/" if phy: b43_path += phy else: # Get the PHY. try: phys = os.listdir(b43_path) except OSError: print "Could not find B43's debugfs directory: %s" % b43_path raise B43Exception if not phys: print "Could not find any b43 device" raise B43Exception if len(phys) != 1: print "Found multiple b43 devices." print "You must call this tool with a phyX parameter to specify a device" raise B43Exception phy = phys[0] b43_path += phy; # Open the debugfs files try: self.f_mmio16read = file(b43_path + "/mmio16read", "r+") self.f_mmio16write = file(b43_path + "/mmio16write", "w") self.f_mmio32read = file(b43_path + "/mmio32read", "r+") self.f_mmio32write = file(b43_path + "/mmio32write", "w") self.f_shm16read = file(b43_path + "/shm16read", "r+") self.f_shm16write = file(b43_path + "/shm16write", "w") self.f_shm32read = file(b43_path + "/shm32read", "r+") self.f_shm32write = file(b43_path + "/shm32write", "w") except IOError, e: print "Could not open debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception self.b43_path = b43_path return # Get the debugfs mountpoint. def __debugfs_find(self): mtab = file("/etc/mtab").read().splitlines() regexp = re.compile(r"^[\w\-_]+\s+([\w/\-_]+)\s+debugfs") path = None for line in mtab: m = regexp.match(line) if m: path = m.group(1) break if not path: print "Could not find debugfs in /etc/mtab" raise B43Exception return path def read16(self, reg): """Do a 16bit MMIO read""" try: self.f_mmio16read.seek(0) self.f_mmio16read.write("0x%X" % reg) self.f_mmio16read.flush() self.f_mmio16read.seek(0) val = self.f_mmio16read.read() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return int(val, 16) def read32(self, reg): """Do a 32bit MMIO read""" try: self.f_mmio32read.seek(0) self.f_mmio32read.write("0x%X" % reg) self.f_mmio32read.flush() self.f_mmio32read.seek(0) val = self.f_mmio32read.read() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return int(val, 16) def maskSet16(self, reg, mask, set): """Do a 16bit MMIO mask-and-set operation""" try: mask &= 0xFFFF set &= 0xFFFF self.f_mmio16write.seek(0) self.f_mmio16write.write("0x%X 0x%X 0x%X" % (reg, mask, set)) self.f_mmio16write.flush() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return def write16(self, reg, value): """Do a 16bit MMIO write""" self.maskSet16(reg, 0, value) return def maskSet32(self, reg, mask, set): """Do a 32bit MMIO mask-and-set operation""" try: mask &= 0xFFFFFFFF set &= 0xFFFFFFFF self.f_mmio32write.seek(0) self.f_mmio32write.write("0x%X 0x%X 0x%X" % (reg, mask, set)) self.f_mmio32write.flush() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return def write32(self, reg, value): """Do a 32bit MMIO write""" self.maskSet32(reg, 0, value) return def shmRead16(self, routing, offset): """Do a 16bit SHM read""" try: self.f_shm16read.seek(0) self.f_shm16read.write("0x%X 0x%X" % (routing, offset)) self.f_shm16read.flush() self.f_shm16read.seek(0) val = self.f_shm16read.read() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return int(val, 16) def shmMaskSet16(self, routing, offset, mask, set): """Do a 16bit SHM mask-and-set operation""" try: mask &= 0xFFFF set &= 0xFFFF self.f_shm16write.seek(0) self.f_shm16write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set)) self.f_shm16write.flush() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return def shmWrite16(self, routing, offset, value): """Do a 16bit SHM write""" self.shmMaskSet16(routing, offset, 0, value) return def shmRead32(self, routing, offset): """Do a 32bit SHM read""" try: self.f_shm32read.seek(0) self.f_shm32read.write("0x%X 0x%X" % (routing, offset)) self.f_shm32read.flush() self.f_shm32read.seek(0) val = self.f_shm32read.read() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return int(val, 16) def shmMaskSet32(self, routing, offset, mask, set): """Do a 32bit SHM mask-and-set operation""" try: mask &= 0xFFFFFFFF set &= 0xFFFFFFFF self.f_shm32write.seek(0) self.f_shm32write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set)) self.f_shm32write.flush() except IOError, e: print "Could not access debugfs file %s: %s" % (e.filename, e.strerror) raise B43Exception return def shmWrite32(self, routing, offset, value): """Do a 32bit SHM write""" self.shmMaskSet32(routing, offset, 0, value) return def getGprs(self): """Returns an array of 64 ints. One for each General Purpose register.""" ret = [] for i in range(0, 64): val = self.shmRead16(B43_SHM_REGS, i) ret.append(val) return ret def getLinkRegs(self): """Returns an array of 4 ints. One for each Link Register.""" ret = [] for i in range(0, 4): val = self.read16(0x4D0 + (i * 2)) ret.append(val) return ret def getOffsetRegs(self): """Returns an array of 7 ints. One for each Offset Register.""" ret = [] for i in range(0, 7): val = self.read16(0x4C0 + (i * 2)) ret.append(val) return ret def shmSharedRead(self): """Returns a string containing the SHM contents.""" ret = "" for i in range(0, 4096, 4): val = self.shmRead32(B43_SHM_SHARED, i) ret += "%c%c%c%c" % (val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, (val >> 24) & 0xFF) return ret def getPsmDebug(self): """Read the PSM-debug register and return an instance of B43PsmDebug.""" val = self.read32(B43_MMIO_PSMDEBUG) return B43PsmDebug(val) def getPsmConditions(self): """This returns the contents of the programmable-PSM-conditions register.""" return self.read16(0x4D8) def ucodeStop(self): """Unconditionally stop the microcode PSM. """ self.maskSet32(B43_MMIO_MACCTL, ~B43_MACCTL_PSM_RUN, 0) return def ucodeStart(self): """Unconditionally start the microcode PSM. This will restart the microcode on the current PC. It will not jump to 0. Warning: This will unconditionally restart the PSM and ignore any driver-state!""" self.maskSet32(B43_MMIO_MACCTL, ~0, B43_MACCTL_PSM_RUN) return class Disassembler: """Disassembler for b43 firmware.""" def __init__(self, binaryText, b43DasmOpts): input = NamedTemporaryFile() output = NamedTemporaryFile() input.write(binaryText) input.flush() #FIXME check b43-dasm errors os.system("b43-dasm %s %s %s" % (input.name, output.name, b43DasmOpts)) self.asmText = output.read() def getAsm(self): """Returns the assembly code.""" return self.asmText class Assembler: """Assembler for b43 firmware.""" def __init__(self, assemblyText, b43AsmOpts): input = NamedTemporaryFile() output = NamedTemporaryFile() input.write(assemblyText) input.flush() #FIXME check b43-asm errors os.system("b43-asm %s %s %s" % (input.name, output.name, b43AsmOpts)) self.binaryText = output.read() def getBinary(self): """Returns the binary code.""" return self.binaryText class TextPatcher: """A textfile patcher that does not include any target context. This can be used to patch b43 firmware files.""" class TextLine: def __init__(self, index, line): self.index = index self.line = line self.deleted = False def __init__(self, text, expected_md5sum): sum = hashlib.md5(text).hexdigest() if sum != expected_md5sum: print "Patcher: The text does not match the expected MD5 sum" print "Expected: " + expected_md5sum print "Calculated: " + sum raise B43Exception text = text.splitlines() self.lines = [] i = 0 for line in text: self.lines.append(TextPatcher.TextLine(i, line)) i += 1 # Add an after-last dummy. Needed for the add-before logic lastDummy = TextPatcher.TextLine(i, "") lastDummy.deleted = True self.lines.append(lastDummy) def getText(self): """This returns the current text.""" textLines = [] for l in self.lines: if not l.deleted: textLines.append(l.line) return "\n".join(textLines) def delLine(self, linenumber): """Delete a line of text. The linenumber corresponds to the original unmodified text.""" for l in self.lines: if l.index == linenumber: l.deleted = True return print "Patcher deleteLine: Did not find the line!" raise B43Exception def addText(self, beforeLineNumber, text): """Add a text before the specified linenumber. The linenumber corresponds to the original unmodified text.""" text = text.splitlines() index = 0 for l in self.lines: if l.index == beforeLineNumber: break index += 1 if index >= len(self.lines): print "Patcher addText: Did not find the line!" raise B43Exception for l in text: self.lines.insert(index, TextPatcher.TextLine(-1, l)) index += 1 class B43SymbolicSpr: """This class converts numeric SPR names into symbolic SPR names.""" def __init__(self, header_file): """The passed header_file parameter is a file path to the assembly file containing the symbolic SPR definitions.""" try: defs = file(header_file).readlines() except IOError, e: print "B43SymbolicSpr: Could not read %s: %s" % (e.filename, e.strerror) B43Exception # Parse the definitions self.spr_names = { } r = re.compile(r"#define\s+(\w+)\s+(spr[a-fA-F0-9]+)") for line in defs: m = r.match(line) if not m: continue # unknown line name = m.group(1) offset = m.group(2) self.spr_names[offset.lower()] = name def get(self, spr): """Get the symbolic name for an SPR. The spr parameter must be a string like "sprXXX", where XXX is a hex number.""" try: spr = self.spr_names[spr.lower()] except KeyError: pass # Symbol not found. Return numeric name. return spr def getRaw(self, spr_hexnumber): """Get the symbolic name for an SPR. The spr_hexnumber parameter is the hexadecimal number for the SPR.""" return self.get("spr%03X" % spr_hexnumber) class B43SymbolicShm: """This class converts numeric SHM offsets into symbolic SHM names.""" def __init__(self, header_file): """The passed header_file parameter is a file path to the assembly file containing the symbolic SHM definitions.""" try: defs = file(header_file).readlines() except IOError, e: print "B43SymbolicShm: Could not read %s: %s" % (e.filename, e.strerror) raise B43Exception # Parse the definitions self.shm_names = { } in_abi_section = False r = re.compile(r"#define\s+(\w+)\s+SHM\((\w+)\).*") for line in defs: if line.startswith("/* BEGIN ABI"): in_abi_section = True if line.startswith("/* END ABI"): in_abi_section = False if not in_abi_section: continue # Only parse ABI definitions m = r.match(line) if not m: continue # unknown line name = m.group(1) offset = int(m.group(2), 16) offset /= 2 self.shm_names[offset] = name def get(self, shm_wordoffset): """Get the symbolic name for an SHM offset.""" try: sym = self.shm_names[shm_wordoffset] except KeyError: # Symbol not found. Return numeric name. sym = "0x%03X" % shm_wordoffset return sym class B43SymbolicCondition: """This class converts numeric External Conditions into symbolic names.""" def __init__(self, header_file): """The passed header_file parameter is a file path to the assembly file containing the symbolic condition definitions.""" try: defs = file(header_file).readlines() except IOError, e: print "B43SymbolicCondition: Could not read %s: %s" % (e.filename, e.strerror) raise B43Exception # Parse the definitions self.cond_names = { } r = re.compile(r"#define\s+(\w+)\s+EXTCOND\(\s*(\w+),\s*(\d+)\s*\).*") for line in defs: m = r.match(line) if not m: continue # unknown line name = m.group(1) register = m.group(2) bit = int(m.group(3)) if register == "CONDREG_RX": register = 0 elif register == "CONDREG_TX": register = 2 elif register == "CONDREG_PHY": register = 3 elif register == "CONDREG_4": register = 4 elif register == "CONDREG_PSM": continue # No lookup table for this one elif register == "CONDREG_RCM": register = 6 elif register == "CONDREG_7": register = 7 else: continue # unknown register cond_number = bit | (register << 4) self.cond_names[cond_number] = name def get(self, cond_number): """Get the symbolic name for an External Condition.""" register = (cond_number >> 4) & 0x7 bit = cond_number & 0xF eoi = ((cond_number & 0x80) != 0) cond_number &= ~0x80 if register == 5: # PSM register return "COND_PSM(%d)" % bit try: sym = self.cond_names[cond_number] except KeyError: # Symbol not found. Return numeric name. sym = "0x%02X" % cond_number if eoi: sym = "EOI(%s)" % sym return sym class B43AsmLine: def __init__(self, text): self.text = text def getLine(self): return self.text def __repr__(self): return self.getLine() def isInstruction(self): return False class B43AsmInstruction(B43AsmLine): def __init__(self, opcode): self.setOpcode(opcode) self.clearOperands() def getOpcode(self): return self.opcode def setOpcode(self, opcode): self.opcode = opcode def clearOperands(self): self.operands = [] def addOperand(self, operand): self.operands.append(operand) def getOperands(self): return self.operands def getLine(self): ret = "\t" + self.opcode if self.operands: ret += "\t" for op in self.operands: ret += op + ", " if self.operands: ret = ret[:-2] return ret def isInstruction(self): return True class B43AsmParser: """A simple B43 assembly code parser.""" def __init__(self, asm_code): self.__parse_code(asm_code) def __parse_code(self, asm_code): self.codelines = [] label = re.compile(r"^\s*\w+:\s*$") insn_0 = re.compile(r"^\s*([@\.\w]+)\s*$") insn_2 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+)\s*$") insn_3 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+)\s*$") insn_5 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+)\s*$") for line in asm_code.splitlines(): m = label.match(line) if m: # Label: l = B43AsmLine(line) self.codelines.append(l) continue m = insn_0.match(line) if m: # No operands insn = B43AsmInstruction(m.group(1)) self.codelines.append(insn) continue m = insn_2.match(line) if m: # Two operands insn = B43AsmInstruction(m.group(1)) insn.addOperand(m.group(2)) insn.addOperand(m.group(3)) self.codelines.append(insn) continue m = insn_3.match(line) if m: # Three operands insn = B43AsmInstruction(m.group(1)) insn.addOperand(m.group(2)) insn.addOperand(m.group(3)) insn.addOperand(m.group(4)) self.codelines.append(insn) continue m = insn_5.match(line) if m: # Three operands insn = B43AsmInstruction(m.group(1)) insn.addOperand(m.group(2)) insn.addOperand(m.group(3)) insn.addOperand(m.group(4)) insn.addOperand(m.group(5)) insn.addOperand(m.group(6)) self.codelines.append(insn) continue # Unknown line l = B43AsmLine(line) self.codelines.append(l) class B43Beautifier(B43AsmParser): """A B43 assembly code beautifier.""" def __init__(self, asm_code, headers_dir): """asm_code is the assembly code. headers_dir is a full path to the directory containing the symbolic SPR,SHM,etc... definitions""" if headers_dir.endswith("/"): headers_dir = headers_dir[:-1] B43AsmParser.__init__(self, asm_code) self.symSpr = B43SymbolicSpr(headers_dir + "/spr.inc") self.symShm = B43SymbolicShm(headers_dir + "/shm.inc") self.symCond = B43SymbolicCondition(headers_dir + "/cond.inc") self.preamble = "#include \"%s/spr.inc\"\n" % headers_dir self.preamble += "#include \"%s/shm.inc\"\n" % headers_dir self.preamble += "#include \"%s/cond.inc\"\n" % headers_dir self.preamble += "\n" self.__process_code() def __process_code(self): spr_re = re.compile(r"^spr\w\w\w$") shm_re = re.compile(r"^\[(0x\w+)\]$") code = self.codelines for line in code: if not line.isInstruction(): continue opcode = line.getOpcode() operands = line.getOperands() # Transform unconditional jump if opcode == "jext" and int(operands[0], 16) == 0x7F: label = operands[1] line.setOpcode("jmp") line.clearOperands() line.addOperand(label) continue # Transform external conditions if opcode == "jext" or opcode == "jnext": operands[0] = self.symCond.get(int(operands[0], 16)) continue # Transform orx 7,8,imm,imm,target to mov if opcode == "orx" and \ int(operands[0], 16) == 7 and int(operands[1], 16) == 8 and\ operands[2].startswith("0x") and operands[3].startswith("0x"): value = int(operands[3], 16) & 0xFF value |= (int(operands[2], 16) & 0xFF) << 8 target = operands[4] line.setOpcode("mov") line.clearOperands() line.addOperand("0x%X" % value) line.addOperand(target) opcode = line.getOpcode() operands = line.getOperands() for i in range(0, len(operands)): o = operands[i] # Transform SPR operands m = spr_re.match(o) if m: operands[i] = self.symSpr.get(o) continue # Transform SHM operands m = shm_re.match(o) if m: offset = int(m.group(1), 16) operands[i] = "[" + self.symShm.get(offset) + "]" continue def getAsm(self): """Returns the beautified asm code.""" ret = [ self.preamble ] for line in self.codelines: ret.append(str(line)) return "\n".join(ret)