debug: Add a generic text patcher
[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         def __init__(self, phy):
64                 debugfs_path = self.__debugfs_find()
65
66                 # Construct the debugfs b43 path to the device
67                 b43_path = debugfs_path + "/b43/"
68                 if phy:
69                         b43_path += phy
70                 else:
71                         # Get the PHY.
72                         phys = os.listdir(b43_path)
73                         if not phys:
74                                 print "Could not find any b43 device"
75                                 raise B43Exception
76                         if len(phys) != 1:
77                                 print "Found multiple b43 devices."
78                                 print "You must call this tool with a phyX parameter to specify a device"
79                                 raise B43Exception
80                         phy = phys[0]
81                         b43_path += phy;
82
83                 # Open the debugfs files
84                 try:
85                         self.f_mmio16read = file(b43_path + "/mmio16read", "r+")
86                         self.f_mmio16write = file(b43_path + "/mmio16write", "w")
87                         self.f_mmio32read = file(b43_path + "/mmio32read", "r+")
88                         self.f_mmio32write = file(b43_path + "/mmio32write", "w")
89                         self.f_shm16read = file(b43_path + "/shm16read", "r+")
90                         self.f_shm16write = file(b43_path + "/shm16write", "w")
91                         self.f_shm32read = file(b43_path + "/shm32read", "r+")
92                         self.f_shm32write = file(b43_path + "/shm32write", "w")
93                 except IOError, e:
94                         print "Could not open debugfs file %s: %s" % (e.filename, e.strerror)
95                         raise B43Exception
96
97                 self.b43_path = b43_path
98                 return
99
100         # Get the debugfs mountpoint.
101         def __debugfs_find(self):
102                 mtab = file("/etc/mtab").read().splitlines()
103                 regexp = re.compile(r"^[\w\-_]+\s+([\w/\-_]+)\s+debugfs")
104                 path = None
105                 for line in mtab:
106                         m = regexp.match(line)
107                         if m:
108                                 path = m.group(1)
109                                 break
110                 if not path:
111                         print "Could not find debugfs in /etc/mtab"
112                         raise B43Exception
113                 return path
114
115         def read16(self, reg):
116                 """Do a 16bit MMIO read"""
117                 try:
118                         self.f_mmio16read.seek(0)
119                         self.f_mmio16read.write("0x%X" % reg)
120                         self.f_mmio16read.flush()
121                         self.f_mmio16read.seek(0)
122                         val = self.f_mmio16read.read()
123                 except IOError, e:
124                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
125                         raise B43Exception
126                 return int(val, 16)
127
128         def read32(self, reg):
129                 """Do a 32bit MMIO read"""
130                 try:
131                         self.f_mmio32read.seek(0)
132                         self.f_mmio32read.write("0x%X" % reg)
133                         self.f_mmio32read.flush()
134                         self.f_mmio32read.seek(0)
135                         val = self.f_mmio32read.read()
136                 except IOError, e:
137                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
138                         raise B43Exception
139                 return int(val, 16)
140
141         def maskSet16(self, reg, mask, set):
142                 """Do a 16bit MMIO mask-and-set operation"""
143                 try:
144                         mask &= 0xFFFF
145                         set &= 0xFFFF
146                         self.f_mmio16write.seek(0)
147                         self.f_mmio16write.write("0x%X 0x%X 0x%X" % (reg, mask, set))
148                         self.f_mmio16write.flush()
149                 except IOError, e:
150                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
151                         raise B43Exception
152                 return
153         
154         def write16(self, reg, value):
155                 """Do a 16bit MMIO write"""
156                 self.maskSet16(reg, 0, value)
157                 return
158
159         def maskSet32(self, reg, mask, set):
160                 """Do a 32bit MMIO mask-and-set operation"""
161                 try:
162                         mask &= 0xFFFFFFFF
163                         set &= 0xFFFFFFFF
164                         self.f_mmio32write.seek(0)
165                         self.f_mmio32write.write("0x%X 0x%X 0x%X" % (reg, mask, set))
166                         self.f_mmio32write.flush()
167                 except IOError, e:
168                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
169                         raise B43Exception
170                 return
171
172         def write32(self, reg, value):
173                 """Do a 32bit MMIO write"""
174                 self.maskSet32(reg, 0, value)
175                 return
176
177         def shmRead16(self, routing, offset):
178                 """Do a 16bit SHM read"""
179                 try:
180                         self.f_shm16read.seek(0)
181                         self.f_shm16read.write("0x%X 0x%X" % (routing, offset))
182                         self.f_shm16read.flush()
183                         self.f_shm16read.seek(0)
184                         val = self.f_shm16read.read()
185                 except IOError, e:
186                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
187                         raise B43Exception
188                 return int(val, 16)
189
190         def shmMaskSet16(self, routing, offset, mask, set):
191                 """Do a 16bit SHM mask-and-set operation"""
192                 try:
193                         mask &= 0xFFFF
194                         set &= 0xFFFF
195                         self.f_shm16write.seek(0)
196                         self.f_shm16write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set))
197                         self.f_shm16write.flush()
198                 except IOError, e:
199                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
200                         raise B43Exception
201                 return
202
203         def shmWrite16(self, routing, offset, value):
204                 """Do a 16bit SHM write"""
205                 self.shmMaskSet16(routing, offset, 0, value)
206                 return
207
208         def shmRead32(self, routing, offset):
209                 """Do a 32bit SHM read"""
210                 try:
211                         self.f_shm32read.seek(0)
212                         self.f_shm32read.write("0x%X 0x%X" % (routing, offset))
213                         self.f_shm32read.flush()
214                         self.f_shm32read.seek(0)
215                         val = self.f_shm32read.read()
216                 except IOError, e:
217                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
218                         raise B43Exception
219                 return int(val, 16)
220
221         def shmMaskSet32(self, routing, offset, mask, set):
222                 """Do a 32bit SHM mask-and-set operation"""
223                 try:
224                         mask &= 0xFFFFFFFF
225                         set &= 0xFFFFFFFF
226                         self.f_shm32write.seek(0)
227                         self.f_shm32write.write("0x%X 0x%X 0x%X 0x%X" % (routing, offset, mask, set))
228                         self.f_shm32write.flush()
229                 except IOError, e:
230                         print "Could not access debugfs file %s: %s" % (e.filename, e.strerror)
231                         raise B43Exception
232                 return
233
234         def shmWrite32(self, routing, offset, value):
235                 """Do a 32bit SHM write"""
236                 self.shmMaskSet32(routing, offset, 0, value)
237                 return
238
239         def getGprs(self):
240                 """Returns an array of 64 ints. One for each General Purpose register."""
241                 ret = []
242                 for i in range(0, 64):
243                         val = self.shmRead16(B43_SHM_REGS, i)
244                         ret.append(val)
245                 return ret
246
247         def getLinkRegs(self):
248                 """Returns an array of 4 ints. One for each Link Register."""
249                 ret = []
250                 for i in range(0, 4):
251                         val = self.read16(0x4D0 + (i * 2))
252                         ret.append(val)
253                 return ret
254
255         def getOffsetRegs(self):
256                 """Returns an array of 7 ints. One for each Offset Register."""
257                 ret = []
258                 for i in range(0, 7):
259                         val = self.read16(0x4C0 + (i * 2))
260                         ret.append(val)
261                 return ret
262
263         def shmSharedRead(self):
264                 """Returns a string containing the SHM contents."""
265                 ret = ""
266                 for i in range(0, 4096, 4):
267                         val = self.shmRead32(B43_SHM_SHARED, i)
268                         ret += "%c%c%c%c" %     (val & 0xFF,
269                                                  (val >> 8) & 0xFF,
270                                                  (val >> 16) & 0xFF,
271                                                  (val >> 24) & 0xFF)
272                 return ret
273
274         def getPsmDebug(self):
275                 """Read the PSM-debug register and return an instance of B43PsmDebug."""
276                 val = self.read32(B43_MMIO_PSMDEBUG)
277                 return B43PsmDebug(val)
278
279         def getPsmConditions(self):
280                 """This returns the contents of the programmable-PSM-conditions register."""
281                 return self.read16(0x4D8)
282
283         def ucodeStop(self):
284                 """Unconditionally stop the microcode PSM. """
285                 self.maskSet32(B43_MMIO_MACCTL, ~B43_MACCTL_PSM_RUN, 0)
286                 return
287
288         def ucodeStart(self):
289                 """Unconditionally start the microcode PSM. This will restart the
290                 microcode on the current PC. It will not jump to 0. Warning: This will
291                 unconditionally restart the PSM and ignore any driver-state!"""
292                 self.maskSet32(B43_MMIO_MACCTL, ~0, B43_MACCTL_PSM_RUN)
293                 return
294
295 class Disassembler:
296         """Disassembler for b43 firmware."""
297         def __init__(self, binaryText, b43DasmOpts):
298                 input = NamedTemporaryFile()
299                 output = NamedTemporaryFile()
300
301                 input.write(binaryText)
302                 input.flush()
303                 #FIXME check b43-dasm errors
304                 os.system("b43-dasm %s %s %s" % (input.name, output.name, b43DasmOpts))
305
306                 self.asmText = output.read()
307
308         def getAsm(self):
309                 """Returns the assembly code."""
310                 return self.asmText
311
312 class Assembler:
313         """Assembler for b43 firmware."""
314         def __init__(self, assemblyText, b43AsmOpts):
315                 input = NamedTemporaryFile()
316                 output = NamedTemporaryFile()
317
318                 input.write(assemblyText)
319                 input.flush()
320                 #FIXME check b43-asm errors
321                 os.system("b43-asm %s %s %s" % (input.name, output.name, b43AsmOpts))
322
323                 self.binaryText = output.read()
324
325         def getBinary(self):
326                 """Returns the binary code."""
327                 return self.binaryText
328
329 class TextPatcher:
330         """A textfile patcher that does not include any target context.
331         This can be used to patch b43 firmware files."""
332
333         class TextLine:
334                 def __init__(self, index, line):
335                         self.index = index
336                         self.line = line
337                         self.deleted = False
338
339         def __init__(self, text, expected_md5sum):
340                 sum = md5.md5(text).hexdigest()
341                 if sum != expected_md5sum:
342                         print "Patcher: The text does not match the expected MD5 sum"
343                         print "Expected:   " + expected_md5sum
344                         print "Calculated: " + sum
345                         raise B43Exception
346                 text = text.splitlines()
347                 self.lines = []
348                 i = 0
349                 for line in text:
350                         self.lines.append(TextPatcher.TextLine(i, line))
351                         i += 1
352                 # Add an after-last dummy. Needed for the add-before logic
353                 lastDummy = TextPatcher.TextLine(i, "")
354                 lastDummy.deleted = True
355                 self.lines.append(lastDummy)
356
357         def getText(self):
358                 """This returns the current text."""
359                 textLines = []
360                 for l in self.lines:
361                         if not l.deleted:
362                                 textLines.append(l.line)
363                 return "\n".join(textLines)
364
365         def delLine(self, linenumber):
366                 """Delete a line of text. The linenumber corresponds to the
367                 original unmodified text."""
368                 for l in self.lines:
369                         if l.index == linenumber:
370                                 l.deleted = True
371                                 return
372                 print "Patcher deleteLine: Did not find the line!"
373                 raise B43Exception
374
375         def addText(self, beforeLineNumber, text):
376                 """Add a text before the specified linenumber. The linenumber
377                 corresponds to the original unmodified text."""
378                 text = text.splitlines()
379                 index = 0
380                 for l in self.lines:
381                         if l.index == beforeLineNumber:
382                                 break
383                         index += 1
384                 if index >= len(self.lines):
385                         print "Patcher addText: Did not find the line!"
386                         raise B43Exception
387                 for l in text:
388                         self.lines.insert(index, TextPatcher.TextLine(-1, l))
389                         index += 1
390