2 # b43 debugging library
4 # Copyright (C) 2008-2010 Michael Buesch <m@bues.ch>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License version 3
8 # as published by the Free Software Foundation.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from tempfile import *
34 class B43Exception(Exception):
38 B43_MMIO_MACCTL = 0x120
39 B43_MMIO_PSMDEBUG = 0x154
41 B43_MACCTL_PSM_MACEN = 0x00000001
42 B43_MACCTL_PSM_RUN = 0x00000002
43 B43_MACCTL_PSM_JMP0 = 0x00000004
44 B43_MACCTL_PSM_DEBUG = 0x00002000
48 """Parse the contents of the PSM-debug register"""
49 def __init__(self, reg_content):
50 self.raw = reg_content
54 """Get the raw PSM-debug register value"""
58 """Get the microcode program counter"""
59 return self.raw & 0xFFF
63 """Hardware access layer. This accesses the hardware through the debugfs interface."""
65 def __init__(self, phy=None):
66 debugfs_path = self.__debugfs_find()
68 # Construct the debugfs b43 path to the device
69 b43_path = debugfs_path + "/b43/"
75 phys = os.listdir(b43_path)
77 print "Could not find B43's debugfs directory: %s" % b43_path
80 print "Could not find any b43 device"
83 print "Found multiple b43 devices."
84 print "You must call this tool with a phyX parameter to specify a device"
89 # Open the debugfs files
91 self.f_mmio16read = file(b43_path + "/mmio16read", "r+")
92 self.f_mmio16write = file(b43_path + "/mmio16write", "w")
93 self.f_mmio32read = file(b43_path + "/mmio32read", "r+")
94 self.f_mmio32write = file(b43_path + "/mmio32write", "w")
95 self.f_shm16read = file(b43_path + "/shm16read", "r+")
96 self.f_shm16write = file(b43_path + "/shm16write", "w")
97 self.f_shm32read = file(b43_path + "/shm32read", "r+")
98 self.f_shm32write = file(b43_path + "/shm32write", "w")
100 print "Could not open debugfs file %s: %s" % (e.filename, e.strerror)
103 self.b43_path = b43_path
106 # Get the debugfs mountpoint.
107 def __debugfs_find(self):
108 mtab = file("/etc/mtab").read().splitlines()
109 regexp = re.compile(r"^[\w\-_]+\s+([\w/\-_]+)\s+debugfs")
112 m = regexp.match(line)
117 print "Could not find debugfs in /etc/mtab"
121 def read16(self, reg):
122 """Do a 16bit MMIO read"""
124 self.f_mmio16read.seek(0)
125 self.f_mmio16read.write("0x%X" % reg)
126 self.f_mmio16read.flush()
127 self.f_mmio16read.seek(0)
128 val = self.f_mmio16read.read()
130 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
134 def read32(self, reg):
135 """Do a 32bit MMIO read"""
137 self.f_mmio32read.seek(0)
138 self.f_mmio32read.write("0x%X" % reg)
139 self.f_mmio32read.flush()
140 self.f_mmio32read.seek(0)
141 val = self.f_mmio32read.read()
143 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
147 def maskSet16(self, reg, mask, set):
148 """Do a 16bit MMIO mask-and-set operation"""
152 self.f_mmio16write.seek(0)
153 self.f_mmio16write.write("0x%X 0x%X 0x%X" % (reg, mask, set))
154 self.f_mmio16write.flush()
156 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
160 def write16(self, reg, value):
161 """Do a 16bit MMIO write"""
162 self.maskSet16(reg, 0, value)
165 def maskSet32(self, reg, mask, set):
166 """Do a 32bit MMIO mask-and-set operation"""
170 self.f_mmio32write.seek(0)
171 self.f_mmio32write.write("0x%X 0x%X 0x%X" % (reg, mask, set))
172 self.f_mmio32write.flush()
174 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
178 def write32(self, reg, value):
179 """Do a 32bit MMIO write"""
180 self.maskSet32(reg, 0, value)
183 def shmRead16(self, routing, offset):
184 """Do a 16bit SHM read"""
186 self.f_shm16read.seek(0)
187 self.f_shm16read.write("0x%X 0x%X" % (routing, offset))
188 self.f_shm16read.flush()
189 self.f_shm16read.seek(0)
190 val = self.f_shm16read.read()
192 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
196 def shmMaskSet16(self, routing, offset, mask, set):
197 """Do a 16bit SHM mask-and-set operation"""
201 self.f_shm16write.seek(0)
202 self.f_shm16write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set))
203 self.f_shm16write.flush()
205 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
209 def shmWrite16(self, routing, offset, value):
210 """Do a 16bit SHM write"""
211 self.shmMaskSet16(routing, offset, 0, value)
214 def shmRead32(self, routing, offset):
215 """Do a 32bit SHM read"""
217 self.f_shm32read.seek(0)
218 self.f_shm32read.write("0x%X 0x%X" % (routing, offset))
219 self.f_shm32read.flush()
220 self.f_shm32read.seek(0)
221 val = self.f_shm32read.read()
223 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
227 def shmMaskSet32(self, routing, offset, mask, set):
228 """Do a 32bit SHM mask-and-set operation"""
232 self.f_shm32write.seek(0)
233 self.f_shm32write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set))
234 self.f_shm32write.flush()
236 print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
240 def shmWrite32(self, routing, offset, value):
241 """Do a 32bit SHM write"""
242 self.shmMaskSet32(routing, offset, 0, value)
246 """Returns an array of 64 ints. One for each General Purpose register."""
248 for i in range(0, 64):
249 val = self.shmRead16(B43_SHM_REGS, i)
253 def getLinkRegs(self):
254 """Returns an array of 4 ints. One for each Link Register."""
256 for i in range(0, 4):
257 val = self.read16(0x4D0 + (i * 2))
261 def getOffsetRegs(self):
262 """Returns an array of 7 ints. One for each Offset Register."""
264 for i in range(0, 7):
265 val = self.read16(0x4C0 + (i * 2))
269 def shmSharedRead(self):
270 """Returns a string containing the SHM contents."""
272 for i in range(0, 4096, 4):
273 val = self.shmRead32(B43_SHM_SHARED, i)
274 ret += "%c%c%c%c" % (val & 0xFF,
280 def getPsmDebug(self):
281 """Read the PSM-debug register and return an instance of B43PsmDebug."""
282 val = self.read32(B43_MMIO_PSMDEBUG)
283 return B43PsmDebug(val)
285 def getPsmConditions(self):
286 """This returns the contents of the programmable-PSM-conditions register."""
287 return self.read16(0x4D8)
290 """Unconditionally stop the microcode PSM. """
291 self.maskSet32(B43_MMIO_MACCTL, ~B43_MACCTL_PSM_RUN, 0)
294 def ucodeStart(self):
295 """Unconditionally start the microcode PSM. This will restart the
296 microcode on the current PC. It will not jump to 0. Warning: This will
297 unconditionally restart the PSM and ignore any driver-state!"""
298 self.maskSet32(B43_MMIO_MACCTL, ~0, B43_MACCTL_PSM_RUN)
302 """Disassembler for b43 firmware."""
303 def __init__(self, binaryText, b43DasmOpts):
304 input = NamedTemporaryFile()
305 output = NamedTemporaryFile()
307 input.write(binaryText)
309 #FIXME check b43-dasm errors
310 os.system("b43-dasm %s %s %s" % (input.name, output.name, b43DasmOpts))
312 self.asmText = output.read()
315 """Returns the assembly code."""
319 """Assembler for b43 firmware."""
320 def __init__(self, assemblyText, b43AsmOpts):
321 input = NamedTemporaryFile()
322 output = NamedTemporaryFile()
324 input.write(assemblyText)
326 #FIXME check b43-asm errors
327 os.system("b43-asm %s %s %s" % (input.name, output.name, b43AsmOpts))
329 self.binaryText = output.read()
332 """Returns the binary code."""
333 return self.binaryText
336 """A textfile patcher that does not include any target context.
337 This can be used to patch b43 firmware files."""
340 def __init__(self, index, line):
345 def __init__(self, text, expected_md5sum):
346 sum = hashlib.md5(text).hexdigest()
347 if sum != expected_md5sum:
348 print "Patcher: The text does not match the expected MD5 sum"
349 print "Expected: " + expected_md5sum
350 print "Calculated: " + sum
352 text = text.splitlines()
356 self.lines.append(TextPatcher.TextLine(i, line))
358 # Add an after-last dummy. Needed for the add-before logic
359 lastDummy = TextPatcher.TextLine(i, "")
360 lastDummy.deleted = True
361 self.lines.append(lastDummy)
364 """This returns the current text."""
368 textLines.append(l.line)
369 return "\n".join(textLines)
371 def delLine(self, linenumber):
372 """Delete a line of text. The linenumber corresponds to the
373 original unmodified text."""
375 if l.index == linenumber:
378 print "Patcher deleteLine: Did not find the line!"
381 def addText(self, beforeLineNumber, text):
382 """Add a text before the specified linenumber. The linenumber
383 corresponds to the original unmodified text."""
384 text = text.splitlines()
387 if l.index == beforeLineNumber:
390 if index >= len(self.lines):
391 print "Patcher addText: Did not find the line!"
394 self.lines.insert(index, TextPatcher.TextLine(-1, l))
397 class B43SymbolicSpr:
398 """This class converts numeric SPR names into symbolic SPR names."""
400 def __init__(self, header_file):
401 """The passed header_file parameter is a file path to the
402 assembly file containing the symbolic SPR definitions."""
404 defs = file(header_file).readlines()
406 print "B43SymbolicSpr: Could not read %s: %s" % (e.filename, e.strerror)
408 # Parse the definitions
410 r = re.compile(r"#define\s+(\w+)\s+(spr[a-fA-F0-9]+)")
414 continue # unknown line
417 self.spr_names[offset.lower()] = name
420 """Get the symbolic name for an SPR. The spr parameter
421 must be a string like "sprXXX", where XXX is a hex number."""
423 spr = self.spr_names[spr.lower()]
425 pass # Symbol not found. Return numeric name.
428 def getRaw(self, spr_hexnumber):
429 """Get the symbolic name for an SPR. The spr_hexnumber
430 parameter is the hexadecimal number for the SPR."""
431 return self.get("spr%03X" % spr_hexnumber)
433 class B43SymbolicShm:
434 """This class converts numeric SHM offsets into symbolic SHM names."""
436 def __init__(self, header_file):
437 """The passed header_file parameter is a file path to the
438 assembly file containing the symbolic SHM definitions."""
440 defs = file(header_file).readlines()
442 print "B43SymbolicShm: Could not read %s: %s" % (e.filename, e.strerror)
444 # Parse the definitions
446 in_abi_section = False
447 r = re.compile(r"#define\s+(\w+)\s+SHM\((\w+)\).*")
449 if line.startswith("/* BEGIN ABI"):
450 in_abi_section = True
451 if line.startswith("/* END ABI"):
452 in_abi_section = False
453 if not in_abi_section:
454 continue # Only parse ABI definitions
457 continue # unknown line
459 offset = int(m.group(2), 16)
461 self.shm_names[offset] = name
463 def get(self, shm_wordoffset):
464 """Get the symbolic name for an SHM offset."""
466 sym = self.shm_names[shm_wordoffset]
468 # Symbol not found. Return numeric name.
469 sym = "0x%03X" % shm_wordoffset
472 class B43SymbolicCondition:
473 """This class converts numeric External Conditions into symbolic names."""
475 def __init__(self, header_file):
476 """The passed header_file parameter is a file path to the
477 assembly file containing the symbolic condition definitions."""
479 defs = file(header_file).readlines()
481 print "B43SymbolicCondition: Could not read %s: %s" % (e.filename, e.strerror)
483 # Parse the definitions
484 self.cond_names = { }
485 r = re.compile(r"#define\s+(\w+)\s+EXTCOND\(\s*(\w+),\s*(\d+)\s*\).*")
489 continue # unknown line
491 register = m.group(2)
492 bit = int(m.group(3))
493 if register == "CONDREG_RX":
495 elif register == "CONDREG_TX":
497 elif register == "CONDREG_PHY":
499 elif register == "CONDREG_4":
501 elif register == "CONDREG_PSM":
502 continue # No lookup table for this one
503 elif register == "CONDREG_RCM":
505 elif register == "CONDREG_7":
508 continue # unknown register
509 cond_number = bit | (register << 4)
510 self.cond_names[cond_number] = name
512 def get(self, cond_number):
513 """Get the symbolic name for an External Condition."""
514 register = (cond_number >> 4) & 0x7
515 bit = cond_number & 0xF
516 eoi = ((cond_number & 0x80) != 0)
518 if register == 5: # PSM register
519 return "COND_PSM(%d)" % bit
521 sym = self.cond_names[cond_number]
523 # Symbol not found. Return numeric name.
524 sym = "0x%02X" % cond_number
526 sym = "EOI(%s)" % sym
530 def __init__(self, text):
537 return self.getLine()
539 def isInstruction(self):
542 class B43AsmInstruction(B43AsmLine):
543 def __init__(self, opcode):
544 self.setOpcode(opcode)
550 def setOpcode(self, opcode):
553 def clearOperands(self):
556 def addOperand(self, operand):
557 self.operands.append(operand)
559 def getOperands(self):
563 ret = "\t" + self.opcode
566 for op in self.operands:
572 def isInstruction(self):
576 """A simple B43 assembly code parser."""
578 def __init__(self, asm_code):
579 self.__parse_code(asm_code)
581 def __parse_code(self, asm_code):
583 label = re.compile(r"^\s*\w+:\s*$")
584 insn_0 = re.compile(r"^\s*([@\.\w]+)\s*$")
585 insn_2 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+)\s*$")
586 insn_3 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+)\s*$")
587 insn_5 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+)\s*$")
588 for line in asm_code.splitlines():
589 m = label.match(line)
592 self.codelines.append(l)
594 m = insn_0.match(line)
596 insn = B43AsmInstruction(m.group(1))
597 self.codelines.append(insn)
599 m = insn_2.match(line)
601 insn = B43AsmInstruction(m.group(1))
602 insn.addOperand(m.group(2))
603 insn.addOperand(m.group(3))
604 self.codelines.append(insn)
606 m = insn_3.match(line)
607 if m: # Three operands
608 insn = B43AsmInstruction(m.group(1))
609 insn.addOperand(m.group(2))
610 insn.addOperand(m.group(3))
611 insn.addOperand(m.group(4))
612 self.codelines.append(insn)
614 m = insn_5.match(line)
615 if m: # Three operands
616 insn = B43AsmInstruction(m.group(1))
617 insn.addOperand(m.group(2))
618 insn.addOperand(m.group(3))
619 insn.addOperand(m.group(4))
620 insn.addOperand(m.group(5))
621 insn.addOperand(m.group(6))
622 self.codelines.append(insn)
626 self.codelines.append(l)
628 class B43Beautifier(B43AsmParser):
629 """A B43 assembly code beautifier."""
631 def __init__(self, asm_code, headers_dir):
632 """asm_code is the assembly code. headers_dir is a full
633 path to the directory containing the symbolic SPR,SHM,etc... definitions"""
634 if headers_dir.endswith("/"):
635 headers_dir = headers_dir[:-1]
636 B43AsmParser.__init__(self, asm_code)
637 self.symSpr = B43SymbolicSpr(headers_dir + "/spr.inc")
638 self.symShm = B43SymbolicShm(headers_dir + "/shm.inc")
639 self.symCond = B43SymbolicCondition(headers_dir + "/cond.inc")
640 self.preamble = "#include \"%s/spr.inc\"\n" % headers_dir
641 self.preamble += "#include \"%s/shm.inc\"\n" % headers_dir
642 self.preamble += "#include \"%s/cond.inc\"\n" % headers_dir
643 self.preamble += "\n"
644 self.__process_code()
646 def __process_code(self):
647 spr_re = re.compile(r"^spr\w\w\w$")
648 shm_re = re.compile(r"^\[(0x\w+)\]$")
649 code = self.codelines
651 if not line.isInstruction():
653 opcode = line.getOpcode()
654 operands = line.getOperands()
655 # Transform unconditional jump
656 if opcode == "jext" and int(operands[0], 16) == 0x7F:
658 line.setOpcode("jmp")
660 line.addOperand(label)
662 # Transform external conditions
663 if opcode == "jext" or opcode == "jnext":
664 operands[0] = self.symCond.get(int(operands[0], 16))
666 # Transform orx 7,8,imm,imm,target to mov
667 if opcode == "orx" and \
668 int(operands[0], 16) == 7 and int(operands[1], 16) == 8 and\
669 operands[2].startswith("0x") and operands[3].startswith("0x"):
670 value = int(operands[3], 16) & 0xFF
671 value |= (int(operands[2], 16) & 0xFF) << 8
673 line.setOpcode("mov")
675 line.addOperand("0x%X" % value)
676 line.addOperand(target)
677 opcode = line.getOpcode()
678 operands = line.getOperands()
679 for i in range(0, len(operands)):
681 # Transform SPR operands
684 operands[i] = self.symSpr.get(o)
686 # Transform SHM operands
689 offset = int(m.group(1), 16)
690 operands[i] = "[" + self.symShm.get(offset) + "]"
694 """Returns the beautified asm code."""
695 ret = [ self.preamble ]
696 for line in self.codelines:
697 ret.append(str(line))
698 return "\n".join(ret)