Add b43 firmware assembly beautifier
[b43-tools.git] / debug / libb43.py
1 """
2 #  b43 debugging library
3 #
4 #  Copyright (C) 2008 Michael Buesch <mb@bu3sch.de>
5 #
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.
9 #
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.
14 #
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/>.
17 """
18
19 import sys
20 import os
21 import re
22 import md5
23 from tempfile import *
24
25
26 # SHM routing values
27 B43_SHM_UCODE           = 0
28 B43_SHM_SHARED          = 1
29 B43_SHM_REGS            = 2
30 B43_SHM_IHR             = 3
31 B43_SHM_RCMTA           = 4
32
33
34 class B43Exception(Exception):
35         pass
36
37
38 B43_MMIO_MACCTL         = 0x120
39 B43_MMIO_PSMDEBUG       = 0x154
40
41 B43_MACCTL_PSM_MACEN    = 0x00000001
42 B43_MACCTL_PSM_RUN      = 0x00000002
43 B43_MACCTL_PSM_JMP0     = 0x00000004
44 B43_MACCTL_PSM_DEBUG    = 0x00002000
45
46
47 class B43PsmDebug:
48         """Parse the contents of the PSM-debug register"""
49         def __init__(self, reg_content):
50                 self.raw = reg_content
51                 return
52
53         def getRaw(self):
54                 """Get the raw PSM-debug register value"""
55                 return self.raw
56
57         def getPc(self):
58                 """Get the microcode program counter"""
59                 return self.raw & 0xFFF
60
61
62 class B43:
63         """Hardware access layer. This accesses the hardware through the debugfs interface."""
64
65         def __init__(self, phy):
66                 debugfs_path = self.__debugfs_find()
67
68                 # Construct the debugfs b43 path to the device
69                 b43_path = debugfs_path + "/b43/"
70                 if phy:
71                         b43_path += phy
72                 else:
73                         # Get the PHY.
74                         phys = os.listdir(b43_path)
75                         if not phys:
76                                 print "Could not find any b43 device"
77                                 raise B43Exception
78                         if len(phys) != 1:
79                                 print "Found multiple b43 devices."
80                                 print "You must call this tool with a phyX parameter to specify a device"
81                                 raise B43Exception
82                         phy = phys[0]
83                         b43_path += phy;
84
85                 # Open the debugfs files
86                 try:
87                         self.f_mmio16read = file(b43_path + "/mmio16read", "r+")
88                         self.f_mmio16write = file(b43_path + "/mmio16write", "w")
89                         self.f_mmio32read = file(b43_path + "/mmio32read", "r+")
90                         self.f_mmio32write = file(b43_path + "/mmio32write", "w")
91                         self.f_shm16read = file(b43_path + "/shm16read", "r+")
92                         self.f_shm16write = file(b43_path + "/shm16write", "w")
93                         self.f_shm32read = file(b43_path + "/shm32read", "r+")
94                         self.f_shm32write = file(b43_path + "/shm32write", "w")
95                 except IOError, e:
96                         print "Could not open debugfs file %s: %s" % (e.filename, e.strerror)
97                         raise B43Exception
98
99                 self.b43_path = b43_path
100                 return
101
102         # Get the debugfs mountpoint.
103         def __debugfs_find(self):
104                 mtab = file("/etc/mtab").read().splitlines()
105                 regexp = re.compile(r"^[\w\-_]+\s+([\w/\-_]+)\s+debugfs")
106                 path = None
107                 for line in mtab:
108                         m = regexp.match(line)
109                         if m:
110                                 path = m.group(1)
111                                 break
112                 if not path:
113                         print "Could not find debugfs in /etc/mtab"
114                         raise B43Exception
115                 return path
116
117         def read16(self, reg):
118                 """Do a 16bit MMIO read"""
119                 try:
120                         self.f_mmio16read.seek(0)
121                         self.f_mmio16read.write("0x%X" % reg)
122                         self.f_mmio16read.flush()
123                         self.f_mmio16read.seek(0)
124                         val = self.f_mmio16read.read()
125                 except IOError, e:
126                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
127                         raise B43Exception
128                 return int(val, 16)
129
130         def read32(self, reg):
131                 """Do a 32bit MMIO read"""
132                 try:
133                         self.f_mmio32read.seek(0)
134                         self.f_mmio32read.write("0x%X" % reg)
135                         self.f_mmio32read.flush()
136                         self.f_mmio32read.seek(0)
137                         val = self.f_mmio32read.read()
138                 except IOError, e:
139                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
140                         raise B43Exception
141                 return int(val, 16)
142
143         def maskSet16(self, reg, mask, set):
144                 """Do a 16bit MMIO mask-and-set operation"""
145                 try:
146                         mask &= 0xFFFF
147                         set &= 0xFFFF
148                         self.f_mmio16write.seek(0)
149                         self.f_mmio16write.write("0x%X 0x%X 0x%X" % (reg, mask, set))
150                         self.f_mmio16write.flush()
151                 except IOError, e:
152                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
153                         raise B43Exception
154                 return
155         
156         def write16(self, reg, value):
157                 """Do a 16bit MMIO write"""
158                 self.maskSet16(reg, 0, value)
159                 return
160
161         def maskSet32(self, reg, mask, set):
162                 """Do a 32bit MMIO mask-and-set operation"""
163                 try:
164                         mask &= 0xFFFFFFFF
165                         set &= 0xFFFFFFFF
166                         self.f_mmio32write.seek(0)
167                         self.f_mmio32write.write("0x%X 0x%X 0x%X" % (reg, mask, set))
168                         self.f_mmio32write.flush()
169                 except IOError, e:
170                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
171                         raise B43Exception
172                 return
173
174         def write32(self, reg, value):
175                 """Do a 32bit MMIO write"""
176                 self.maskSet32(reg, 0, value)
177                 return
178
179         def shmRead16(self, routing, offset):
180                 """Do a 16bit SHM read"""
181                 try:
182                         self.f_shm16read.seek(0)
183                         self.f_shm16read.write("0x%X 0x%X" % (routing, offset))
184                         self.f_shm16read.flush()
185                         self.f_shm16read.seek(0)
186                         val = self.f_shm16read.read()
187                 except IOError, e:
188                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
189                         raise B43Exception
190                 return int(val, 16)
191
192         def shmMaskSet16(self, routing, offset, mask, set):
193                 """Do a 16bit SHM mask-and-set operation"""
194                 try:
195                         mask &= 0xFFFF
196                         set &= 0xFFFF
197                         self.f_shm16write.seek(0)
198                         self.f_shm16write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set))
199                         self.f_shm16write.flush()
200                 except IOError, e:
201                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
202                         raise B43Exception
203                 return
204
205         def shmWrite16(self, routing, offset, value):
206                 """Do a 16bit SHM write"""
207                 self.shmMaskSet16(routing, offset, 0, value)
208                 return
209
210         def shmRead32(self, routing, offset):
211                 """Do a 32bit SHM read"""
212                 try:
213                         self.f_shm32read.seek(0)
214                         self.f_shm32read.write("0x%X 0x%X" % (routing, offset))
215                         self.f_shm32read.flush()
216                         self.f_shm32read.seek(0)
217                         val = self.f_shm32read.read()
218                 except IOError, e:
219                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
220                         raise B43Exception
221                 return int(val, 16)
222
223         def shmMaskSet32(self, routing, offset, mask, set):
224                 """Do a 32bit SHM mask-and-set operation"""
225                 try:
226                         mask &= 0xFFFFFFFF
227                         set &= 0xFFFFFFFF
228                         self.f_shm32write.seek(0)
229                         self.f_shm32write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set))
230                         self.f_shm32write.flush()
231                 except IOError, e:
232                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
233                         raise B43Exception
234                 return
235
236         def shmWrite32(self, routing, offset, value):
237                 """Do a 32bit SHM write"""
238                 self.shmMaskSet32(routing, offset, 0, value)
239                 return
240
241         def getGprs(self):
242                 """Returns an array of 64 ints. One for each General Purpose register."""
243                 ret = []
244                 for i in range(0, 64):
245                         val = self.shmRead16(B43_SHM_REGS, i)
246                         ret.append(val)
247                 return ret
248
249         def getLinkRegs(self):
250                 """Returns an array of 4 ints. One for each Link Register."""
251                 ret = []
252                 for i in range(0, 4):
253                         val = self.read16(0x4D0 + (i * 2))
254                         ret.append(val)
255                 return ret
256
257         def getOffsetRegs(self):
258                 """Returns an array of 7 ints. One for each Offset Register."""
259                 ret = []
260                 for i in range(0, 7):
261                         val = self.read16(0x4C0 + (i * 2))
262                         ret.append(val)
263                 return ret
264
265         def shmSharedRead(self):
266                 """Returns a string containing the SHM contents."""
267                 ret = ""
268                 for i in range(0, 4096, 4):
269                         val = self.shmRead32(B43_SHM_SHARED, i)
270                         ret += "%c%c%c%c" %     (val & 0xFF,
271                                                  (val >> 8) & 0xFF,
272                                                  (val >> 16) & 0xFF,
273                                                  (val >> 24) & 0xFF)
274                 return ret
275
276         def getPsmDebug(self):
277                 """Read the PSM-debug register and return an instance of B43PsmDebug."""
278                 val = self.read32(B43_MMIO_PSMDEBUG)
279                 return B43PsmDebug(val)
280
281         def getPsmConditions(self):
282                 """This returns the contents of the programmable-PSM-conditions register."""
283                 return self.read16(0x4D8)
284
285         def ucodeStop(self):
286                 """Unconditionally stop the microcode PSM. """
287                 self.maskSet32(B43_MMIO_MACCTL, ~B43_MACCTL_PSM_RUN, 0)
288                 return
289
290         def ucodeStart(self):
291                 """Unconditionally start the microcode PSM. This will restart the
292                 microcode on the current PC. It will not jump to 0. Warning: This will
293                 unconditionally restart the PSM and ignore any driver-state!"""
294                 self.maskSet32(B43_MMIO_MACCTL, ~0, B43_MACCTL_PSM_RUN)
295                 return
296
297 class Disassembler:
298         """Disassembler for b43 firmware."""
299         def __init__(self, binaryText, b43DasmOpts):
300                 input = NamedTemporaryFile()
301                 output = NamedTemporaryFile()
302
303                 input.write(binaryText)
304                 input.flush()
305                 #FIXME check b43-dasm errors
306                 os.system("b43-dasm %s %s %s" % (input.name, output.name, b43DasmOpts))
307
308                 self.asmText = output.read()
309
310         def getAsm(self):
311                 """Returns the assembly code."""
312                 return self.asmText
313
314 class Assembler:
315         """Assembler for b43 firmware."""
316         def __init__(self, assemblyText, b43AsmOpts):
317                 input = NamedTemporaryFile()
318                 output = NamedTemporaryFile()
319
320                 input.write(assemblyText)
321                 input.flush()
322                 #FIXME check b43-asm errors
323                 os.system("b43-asm %s %s %s" % (input.name, output.name, b43AsmOpts))
324
325                 self.binaryText = output.read()
326
327         def getBinary(self):
328                 """Returns the binary code."""
329                 return self.binaryText
330
331 class TextPatcher:
332         """A textfile patcher that does not include any target context.
333         This can be used to patch b43 firmware files."""
334
335         class TextLine:
336                 def __init__(self, index, line):
337                         self.index = index
338                         self.line = line
339                         self.deleted = False
340
341         def __init__(self, text, expected_md5sum):
342                 sum = md5.md5(text).hexdigest()
343                 if sum != expected_md5sum:
344                         print "Patcher: The text does not match the expected MD5 sum"
345                         print "Expected:   " + expected_md5sum
346                         print "Calculated: " + sum
347                         raise B43Exception
348                 text = text.splitlines()
349                 self.lines = []
350                 i = 0
351                 for line in text:
352                         self.lines.append(TextPatcher.TextLine(i, line))
353                         i += 1
354                 # Add an after-last dummy. Needed for the add-before logic
355                 lastDummy = TextPatcher.TextLine(i, "")
356                 lastDummy.deleted = True
357                 self.lines.append(lastDummy)
358
359         def getText(self):
360                 """This returns the current text."""
361                 textLines = []
362                 for l in self.lines:
363                         if not l.deleted:
364                                 textLines.append(l.line)
365                 return "\n".join(textLines)
366
367         def delLine(self, linenumber):
368                 """Delete a line of text. The linenumber corresponds to the
369                 original unmodified text."""
370                 for l in self.lines:
371                         if l.index == linenumber:
372                                 l.deleted = True
373                                 return
374                 print "Patcher deleteLine: Did not find the line!"
375                 raise B43Exception
376
377         def addText(self, beforeLineNumber, text):
378                 """Add a text before the specified linenumber. The linenumber
379                 corresponds to the original unmodified text."""
380                 text = text.splitlines()
381                 index = 0
382                 for l in self.lines:
383                         if l.index == beforeLineNumber:
384                                 break
385                         index += 1
386                 if index >= len(self.lines):
387                         print "Patcher addText: Did not find the line!"
388                         raise B43Exception
389                 for l in text:
390                         self.lines.insert(index, TextPatcher.TextLine(-1, l))
391                         index += 1
392
393 class B43SymbolicSpr:
394         """This class converts numeric SPR names into symbolic SPR names."""
395
396         def __init__(self, header_file):
397                 """The passed header_file parameter is a file path to the
398                 assembly file containing the symbolic SPR definitions."""
399                 try:
400                         defs = file(header_file).readlines()
401                 except IOError, e:
402                         print "B43SymbolicSpr: Could not read %s: %s" % (e.filename, e.strerror)
403                         B43Exception
404                 # Parse the definitions
405                 self.spr_names = { }
406                 r = re.compile(r"#define\s+(\w+)\s+(spr[a-fA-F0-9]+)")
407                 for line in defs:
408                         m = r.match(line)
409                         if not m:
410                                 continue # unknown line
411                         name = m.group(1)
412                         offset = m.group(2)
413                         self.spr_names[offset.lower()] = name
414
415         def get(self, spr):
416                 """Get the symbolic name for an SPR. The spr parameter
417                 must be a string like "sprXXX", where XXX is a hex number."""
418                 try:
419                         spr = self.spr_names[spr.lower()]
420                 except KeyError:
421                         pass # Symbol not found. Return numeric name.
422                 return spr
423
424         def getRaw(self, spr_hexnumber):
425                 """Get the symbolic name for an SPR. The spr_hexnumber
426                 parameter is the hexadecimal number for the SPR."""
427                 return self.get("spr%03X" % spr_hexnumber)
428
429 class B43SymbolicShm:
430         """This class converts numeric SHM offsets into symbolic SHM names."""
431
432         def __init__(self, header_file):
433                 """The passed header_file parameter is a file path to the
434                 assembly file containing the symbolic SHM definitions."""
435                 try:
436                         defs = file(header_file).readlines()
437                 except IOError, e:
438                         print "B43SymbolicShm: Could not read %s: %s" % (e.filename, e.strerror)
439                         raise B43Exception
440                 # Parse the definitions
441                 self.shm_names = { }
442                 in_abi_section = False
443                 r = re.compile(r"#define\s+(\w+)\s+SHM\((\w+)\).*")
444                 for line in defs:
445                         if line.startswith("/* BEGIN ABI"):
446                                 in_abi_section = True
447                         if line.startswith("/* END ABI"):
448                                 in_abi_section = False
449                         if not in_abi_section:
450                                 continue # Only parse ABI definitions
451                         m = r.match(line)
452                         if not m:
453                                 continue # unknown line
454                         name = m.group(1)
455                         offset = int(m.group(2), 16)
456                         offset /= 2
457                         self.shm_names[offset] = name
458
459         def get(self, shm_wordoffset):
460                 """Get the symbolic name for an SHM offset."""
461                 try:
462                         sym = self.shm_names[shm_wordoffset]
463                 except KeyError:
464                         # Symbol not found. Return numeric name.
465                         sym = "0x%03X" % shm_wordoffset
466                 return sym
467
468 class B43SymbolicCondition:
469         """This class converts numeric External Conditions into symbolic names."""
470
471         def __init__(self, header_file):
472                 """The passed header_file parameter is a file path to the
473                 assembly file containing the symbolic condition definitions."""
474                 try:
475                         defs = file(header_file).readlines()
476                 except IOError, e:
477                         print "B43SymbolicCondition: Could not read %s: %s" % (e.filename, e.strerror)
478                         raise B43Exception
479                 # Parse the definitions
480                 self.cond_names = { }
481                 r = re.compile(r"#define\s+(\w+)\s+EXTCOND\(\s*(\w+),\s*(\d+)\s*\).*")
482                 for line in defs:
483                         m = r.match(line)
484                         if not m:
485                                 continue # unknown line
486                         name = m.group(1)
487                         register = m.group(2)
488                         bit = int(m.group(3))
489                         if register == "CONDREG_RX":
490                                 register = 0
491                         elif register == "CONDREG_TX":
492                                 register = 2
493                         elif register == "CONDREG_PHY":
494                                 register = 3
495                         elif register == "CONDREG_4":
496                                 register = 4
497                         elif register == "CONDREG_PSM":
498                                 continue # No lookup table for this one
499                         elif register == "CONDREG_RCM":
500                                 register = 6
501                         elif register == "CONDREG_7":
502                                 register = 7
503                         else:
504                                 continue # unknown register
505                         cond_number = bit | (register << 4)
506                         self.cond_names[cond_number] = name
507
508         def get(self, cond_number):
509                 """Get the symbolic name for an External Condition."""
510                 register = (cond_number >> 4) & 0x7
511                 bit = cond_number & 0xF
512                 eoi = ((cond_number & 0x80) != 0)
513                 cond_number &= ~0x80
514                 if register == 5: # PSM register
515                         return "COND_PSM(%d)" % bit
516                 try:
517                         sym = self.cond_names[cond_number]
518                 except KeyError:
519                         # Symbol not found. Return numeric name.
520                         sym = "0x%02X" % cond_number
521                 if eoi:
522                         sym = "EOI(%s)" % sym
523                 return sym
524
525 class B43AsmLine:
526         def __init__(self, text):
527                 self.text = text
528
529         def getLine(self):
530                 return self.text
531
532         def isInstruction(self):
533                 return False
534
535 class B43AsmInstruction(B43AsmLine):
536         def __init__(self, opcode):
537                 self.opcode = opcode
538                 self.operands = []
539
540         def getOpcode(self):
541                 return self.opcode
542
543         def addOperand(self, operand):
544                 self.operands.append(operand)
545
546         def getOperands(self):
547                 return self.operands
548
549         def getLine(self):
550                 ret = "\t" + self.opcode
551                 if self.operands:
552                         ret += "\t"
553                 for op in self.operands:
554                         ret += op + ", "
555                 if self.operands:
556                         ret = ret[:-2]
557                 return ret
558
559         def isInstruction(self):
560                 return True
561
562 class B43AsmParser:
563         """A simple B43 assembly code parser."""
564
565         def __init__(self, asm_code):
566                 self.__parse_code(asm_code)
567
568         def __parse_code(self, asm_code):
569                 self.codelines = []
570                 label = re.compile(r"^\s*\w+:\s*$")
571                 insn_0 = re.compile(r"^\s*([@\.\w]+)\s*$")
572                 insn_2 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+)\s*$")
573                 insn_3 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+)\s*$")
574                 insn_5 = re.compile(r"^\s*([@\.\w]+)\s+([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+),\s*([@\[\],\w]+)\s*$")
575                 for line in asm_code.splitlines():
576                         m = label.match(line)
577                         if m: # Label:
578                                 l = B43AsmLine(line)
579                                 self.codelines.append(l)
580                                 continue
581                         m = insn_0.match(line)
582                         if m: # No operands
583                                 insn = B43AsmInstruction(m.group(1))
584                                 self.codelines.append(insn)
585                                 continue
586                         m = insn_2.match(line)
587                         if m: # Two operands
588                                 insn = B43AsmInstruction(m.group(1))
589                                 insn.addOperand(m.group(2))
590                                 insn.addOperand(m.group(3))
591                                 self.codelines.append(insn)
592                                 continue
593                         m = insn_3.match(line)
594                         if m: # Three operands
595                                 insn = B43AsmInstruction(m.group(1))
596                                 insn.addOperand(m.group(2))
597                                 insn.addOperand(m.group(3))
598                                 insn.addOperand(m.group(4))
599                                 self.codelines.append(insn)
600                                 continue
601                         m = insn_5.match(line)
602                         if m: # Three operands
603                                 insn = B43AsmInstruction(m.group(1))
604                                 insn.addOperand(m.group(2))
605                                 insn.addOperand(m.group(3))
606                                 insn.addOperand(m.group(4))
607                                 insn.addOperand(m.group(5))
608                                 insn.addOperand(m.group(6))
609                                 self.codelines.append(insn)
610                                 continue
611                         # Unknown line
612                         l = B43AsmLine(line)
613                         self.codelines.append(l)
614
615 class B43Beautifier(B43AsmParser):
616         """A B43 assembly code beautifier."""
617
618         def __init__(self, asm_code, headers_dir):
619                 """asm_code is the assembly code. headers_dir is a full
620                 path to the directory containing the symbolic SPR,SHM,etc... definitions"""
621                 B43AsmParser.__init__(self, asm_code)
622                 self.symSpr = B43SymbolicSpr(headers_dir + "/spr.inc")
623                 self.symShm = B43SymbolicShm(headers_dir + "/shm.inc")
624                 self.symCond = B43SymbolicCondition(headers_dir + "/cond.inc")
625                 self.preamble = "#include <%s/spr.inc>\n" % headers_dir
626                 self.preamble += "#include <%s/shm.inc>\n" % headers_dir
627                 self.preamble += "#include <%s/cond.inc>\n" % headers_dir
628                 self.preamble += "\n"
629                 self.__process_code()
630
631         def __process_code(self):
632                 spr_re = re.compile(r"^spr\w\w\w$")
633                 shm_re = re.compile(r"^\[(0x\w+)\]$")
634                 code = self.codelines
635                 for line in code:
636                         if not line.isInstruction():
637                                 continue
638                         opcode = line.getOpcode()
639                         operands = line.getOperands()
640                         if opcode == "jext" or opcode == "jnext":
641                                 operands[0] = self.symCond.get(int(operands[0], 16))
642                                 continue
643                         for i in range(0, len(operands)):
644                                 o = operands[i]
645                                 m = spr_re.match(o)
646                                 if m:
647                                         operands[i] = self.symSpr.get(o)
648                                         continue
649                                 m = shm_re.match(o)
650                                 if m:
651                                         offset = int(m.group(1), 16)
652                                         operands[i] = "[" + self.symShm.get(offset) + "]"
653                                         continue
654
655         def getAsm(self):
656                 """Returns the beautified asm code."""
657                 ret = self.preamble
658                 for line in self.codelines:
659                         ret += line.getLine() + "\n"
660                 return ret