nexmon – Rev 1

Subversion Repositories:
Rev:
"""
#  b43 debugging library
#
#  Copyright (C) 2008-2010 Michael Buesch <m@bues.ch>
#
#  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 <http://www.gnu.org/licenses/>.
"""

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)