Subversion Repositories freemyipod

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
82 benedikt93 1
#!/usr/bin/env python
64 benedikt93 2
#
3
#
171 farthen 4
#    Copyright 2010 TheSeven, benedikt93, Farthen
64 benedikt93 5
#
6
#
82 benedikt93 7
#    This file is part of emBIOS.
64 benedikt93 8
#
82 benedikt93 9
#    emBIOS is free software: you can redistribute it and/or
64 benedikt93 10
#    modify it under the terms of the GNU General Public License as
11
#    published by the Free Software Foundation, either version 2 of the
12
#    License, or (at your option) any later version.
13
#
82 benedikt93 14
#    emBIOS is distributed in the hope that it will be useful,
64 benedikt93 15
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
16
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
#    See the GNU General Public License for more details.
18
#
82 benedikt93 19
#    You should have received a copy of the GNU General Public License
20
#    along with emBIOS.  If not, see <http://www.gnu.org/licenses/>.
64 benedikt93 21
#
22
#
23
 
171 farthen 24
import sys
25
import os
26
import inspect
27
import re
64 benedikt93 28
 
29
import libembios
171 farthen 30
from libembios import Error
31
import libembiosdata
64 benedikt93 32
 
171 farthen 33
class NotImplementedError(Error):
34
    pass
64 benedikt93 35
 
171 farthen 36
class ArgumentError(Error):
37
    pass
64 benedikt93 38
 
171 farthen 39
class ArgumentTypeError(Error):
40
    def __init__(self, expected, seen=False):
41
        self.expected = expected
42
        self.seen = seen
43
    def __str__(self):
44
        if self.seen:
45
            return "Expected " + str(self.expected) + " but saw " + str(self.seen)
46
        else:
47
            return "Expected " + str(self.expected) + ", but saw something else"
64 benedikt93 48
 
49
 
171 farthen 50
def usage(errormsg=None, specific=False):
51
    """
52
        Prints the usage information.
53
        It is auto generated from various places.
54
    """
55
    logger = Logger()
56
    cmddict= Commandline.cmddict
57
    doc = {}
58
    # This sorts the output of various internal functions
59
    # and puts everything in easy readable form
60
    for function in cmddict:
61
        function = cmddict[function].func
62
        docinfo = {}
63
        name = function.__name__
64
        args = inspect.getargspec(function)[0]
65
        docinfo['varargs'] = False
66
        if inspect.getargspec(function)[1]:
67
            docinfo['varargs'] = True
68
        kwargvalues = inspect.getargspec(function)[3]
69
        kwargs = {}
70
        if args:
71
            if kwargvalues:
72
                argnum = len(args) - len(kwargvalues)
73
                kwargnum = len(kwargvalues)
74
                kwargs = dict(zip(args[argnum:], kwargvalues))
75
            else:
76
                argnum = len(args)
77
        else:
78
            argnum = 0
79
        docinfo['args'] = args[1:argnum]
80
        docinfo['kwargs'] = kwargs
81
        if function.__doc__:
82
            # strip unneccessary whitespace
83
            docinfo['documentation'] = re.sub(r'\n        ', '\n', function.__doc__)
84
        else:
85
            docinfo['documentation'] = None
86
        doc[name] = docinfo
64 benedikt93 87
 
171 farthen 88
    if not specific:
89
        logger.log("Please provide a command and (if needed) parameters as command line arguments\n\n")
90
        logger.log("Available commands:\n\n")
82 benedikt93 91
    else:
171 farthen 92
        logger.log("\n")
93
    for function in sorted(doc.items()):
94
        function = function[0]
95
        if specific == False or specific == function:
96
            logger.log("  " + function + " ")
97
            for arg in doc[function]['args']:
98
                logger.log("<" + arg + "> ")
99
            if doc[function]['kwargs']:
100
                for kwarg in doc[function]['kwargs']:
101
                    logger.log("[" + kwarg + "] ")
102
            if doc[function]['varargs']:
103
                logger.log("<db1> ... <dbN>")
104
            if doc[function]['documentation']:
105
                logger.log(doc[function]['documentation']+"\n")
64 benedikt93 106
 
171 farthen 107
    logger.log("\n")
108
 
109
    if errormsg:
110
        logger.error(str(errormsg)+"\n")
111
    exit(2)
112
 
113
 
114
class Logger(object):
115
    """
116
        Simple stdout logger.
117
        Loglevel 4 is most verbose, Loglevel 0 only say something if there is an error.
118
    """
119
    def __init__(self):
120
        # Possible values: 0 (only errors), 1 (warnings), 2 (info, recommended for production use), 3 and more (debug)
121
        self.loglevel = 3
122
 
123
    def log(self, text):
124
        sys.stdout.write(text)
67 benedikt93 125
 
171 farthen 126
    def debug(self, text):
127
        if self.loglevel >= 3:
128
            self.log(text)
119 benedikt93 129
 
171 farthen 130
    def info(self, text):
131
        if self.loglevel >= 2:
132
            self.log(text)
82 benedikt93 133
 
171 farthen 134
    def warning(self, text):
135
        if self.loglevel >= 1:
136
            self.log("WARNING: " + text)
119 benedikt93 137
 
171 farthen 138
    def error(self, text):
139
        self.log("ERROR: " + text)
140
 
141
 
142
def command(func):
143
    """
144
        Decorator for all commands.
145
        The decorated function is called with (self, all, other, arguments, ...)
146
    """
172 farthen 147
    def decorator(*args):
171 farthen 148
        return func(args[0], *args[1:])
149
    func._command = True
150
    decorator.func = func
151
    return decorator
152
 
153
 
154
def commandClass(cls):
155
    """
156
        Decorator for the class. Sets the self.cmddict of the class
157
        to all functions decorated with @command
158
    """
159
    cls.cmddict = {}
160
    for attr, value in cls.__dict__.iteritems():
161
        if getattr(value, 'func', False):
162
            if getattr(value.func, '_command', False):
163
                cls.cmddict[value.func.__name__] = value
164
    return cls
165
 
166
 
167
@commandClass
168
class Commandline(object):
169
    """
170
        If you want to create a new commandline function you just need to
171
        create a function with the name of it in this class and decorate
172
        it with the decorator @command. If you don't want to call the desired
173
        function (wrong arguments etc) just raise ArgumentError with or
174
        without an error message or raise ArgumentCountError
175
    """
176
    def __init__(self):
177
        self.logger = Logger()
178
        try:
179
            self.embios = libembios.Embios()
180
        except libembios.DeviceNotFoundError:
181
            self.logger.error("No emBIOS device found!")
182
            end(1)
172 farthen 183
        self.getinfo("version")
171 farthen 184
 
185
    def _parsecommand(self, func, args):
186
        # adds self to the commandline args.
187
        # this is needed because the functions need access to their class.
188
        args.insert(0, self)
189
        if func in self.cmddict:
190
            try:
172 farthen 191
                self.cmddict[func](*args)
171 farthen 192
            except ArgumentError, e:
193
                usage(e)
194
            except ArgumentError:
195
                usage("Syntax Error in function '" + func + "'")
196
            except ArgumentTypeError, e:
197
                usage(e)
198
            except NotImplementedError:
199
                self.logger.error("This function is not implemented yet!")
172 farthen 200
            except libembios.DeviceNotFoundError:
201
                self.logger.error("Device not found!")
171 farthen 202
            except libembios.DeviceError, e:
203
                self.logger.error(str(e))
204
            except TypeError, e:
205
                if str(e).split(" ", 1)[0] == func + "()":
206
                    self.logger.error(usage("Argument Error in '" + func + "': Wrong argument count", specific=func))
207
                else:
208
                    raise
209
        else:
210
            usage("No such command")
67 benedikt93 211
 
171 farthen 212
    @staticmethod
213
    def _bool(something):
214
        """
215
            Converts quite everything into bool.
216
        """
217
        if type(something) == bool:
218
            return something
219
        elif type(something) == int or type(something) == long:
220
            return bool(something)
221
        elif type(something == str):
222
            truelist = ['true', '1', 't', 'y', 'yes']
223
            falselist = ['false', '0', 'f', 'n', 'no']
224
            if something.lower() in truelist:
225
                return True
226
            elif something.lower() in falselist:
227
                return False
228
        raise ArgumentTypeError("bool", "'"+str(something)+"'")
229
 
230
    @staticmethod
231
    def _hexint(something):
232
        """
233
            Converts quite everything to a hexadecimal represented integer.
234
            This works for default arguments too, because it returns
235
            None when it found that it got a NoneType object.
236
        """
237
        if type(something) == int or type(something) == long:
238
            return something
239
        elif type(something) == str:
240
            try:
241
                return int(something, 16)
242
            except ValueError:
243
                raise ArgumentTypeError("hexadecimal coded integer", "'"+str(something)+"'")
244
        elif type(something) == NoneType:
245
            return None
246
        else:
247
            raise ArgumentTypeError("hexadecimal coded integer", "'"+str(something)+"'")
248
 
249
    @staticmethod
250
    def _strcheck(string, values):
251
        if string in values:
252
            return string
253
        else:
254
            expected = ""
255
            for item in values:
256
                expected += "'" + item + "', "
257
            expected = expected[:-2]
258
            raise ArgumentTypeError("one out of " + expected, "'" + string + "'")
64 benedikt93 259
 
260
 
171 farthen 261
    @command
262
    def getinfo(self, infotype):
263
        """
264
            Get info on the running emBIOS.
265
            <infotype> may be either of 'version', 'packetsize', 'usermemrange'.
266
        """
267
        if infotype == "version":
268
            resp = self.embios.getversioninfo()
172 farthen 269
            self.logger.info("Connected to "+libembiosdata.swtypes[resp.swtypeid] + " v" + str(resp.majorv) + "." + str(resp.minorv) +
171 farthen 270
                             "." + str(resp.patchv) + " r" + str(resp.revision) + " running on " + libembiosdata.hwtypes[resp.hwtypeid] + "\n")
271
        elif infotype == "packetsize":
272
            resp = self.embios.getpacketsizeinfo()
273
            self.logger.info("Maximum packet sizes: "+str(resp))
274
        elif infotype == "usermemrange":
275
            resp = self.embios.getusermemrange()
276
            self.logger.info("The user memory range is "+hex(resp.lower)+" - "+hex(resp.upper-1))
277
        else:
278
            raise ArgumentTypeError("one out of 'version', 'packetsize', 'usermemrange'", infotype)
64 benedikt93 279
 
171 farthen 280
    @command
281
    def reset(self, force=False):
282
        """
283
            Resets the device"
284
            If <force> is 1, the reset will be forced, otherwise it will be gracefully,
285
            which may take some time.
286
        """
287
        force = self._bool(force)
288
        if force: self.logger.info("Resetting forcefully...\n")
289
        else: self.logger.info("Resetting...\n")
290
        self.embios.reset(force)
64 benedikt93 291
 
171 farthen 292
    @command
293
    def poweroff(self, force=False):
294
        """
295
            Powers the device off
296
            If <force> is 1, the poweroff will be forced, otherwise it will be gracefully,
297
            which may take some time.
298
        """
299
        force = self._bool(force)
300
        if force: self.logger.info("Resetting forcefully...\n")
301
        else: self.logger.info("Resetting...\n")
302
        self.embios.reset(force)
64 benedikt93 303
 
171 farthen 304
    @command
305
    def uploadfile(self, addr, filename):
306
        """
307
            Uploads a file to the device
308
            <offset>: the address to upload the file to
309
            <filename>: the path to the file
310
        """
311
        addr = self._hexint(addr)
312
        try:
313
            f = open(filename, 'rb')
314
        except IOError:
315
            raise ArgumentError("File not readable. Does it exist?")
316
        self.logger.info("Writing file '"+filename+"' to memory at "+hex(addr)+"...")
317
        with f:
318
            self.embios.write(addr, f.read())
319
        self.logger.info("done\n")
320
 
321
 
322
 
323
    @command
324
    def downloadfile(self, addr, size, filename):
325
        """
326
            Uploads a file to the device
327
            <offset>: the address to upload the file to
328
            <size>: the number of bytes to be read
329
            <filename>: the path to the file
330
        """
331
        addr = self._hexint(addr)
332
        size = self._hexint(size)
333
        try:
334
            f = open(filename, 'wb')
335
        except IOError:
336
            raise ArgumentError("Can not open file for write!")
337
        self.logger.info("Reading data from address "+hex(addr)+" with the size "+hex(size)+" to '"+filename+"'...")
338
        with f:
339
            f.write(self.embios.read(addr, size))
340
        self.logger.info("done\n")
341
 
342
    @command
343
    def uploadint(self, addr, integer):
344
        """
345
            Uploads a single integer to the device
346
            <offset>: the address to upload the integer to
347
            <data>: the integer to upload
348
        """
349
        addr = self._hexint(addr)
350
        integer = self._hexint(integer)
351
        if integer > 0xFFFFFFFF:
352
            raise ArgumentError("Specified integer too long")
353
        data = chr(integer)
354
        self.embios.writemem(addr, data)
355
        self.logger.info("Integer '"+hex(integer)+"' written successfully to "+hex(addr))
356
 
357
    @command
358
    def downloadint(self, addr):
359
        """
360
            Downloads a single integer from the device and prints it to the console window
361
            <offset>: the address to download the integer from
362
        """
363
        addr = self._hexint(addr)
364
        data = self.embios.readmem(addr, 1)
365
        integer = ord(data)
366
        self.logger.info("Integer '"+hex(integer)+"' read from address "+hex(addr))
367
 
368
    @command
369
    def i2crecv(self, bus, slave, addr, size):
370
        """
371
            Reads data from an I2C device
372
            <bus> the bus index
373
            <slave> the slave address
374
            <addr> the start address on the I2C device
375
            <size> the number of bytes to read
376
        """
377
        bus = self._hexint(bus)
378
        slave = self._hexint(slave)
379
        addr = self._hexint(addr)
380
        size = self._hexint(size)
381
        raise NotImplementedError
382
 
383
    @command
384
    def i2csend(self, bus, slave, addr, *args):
385
        """
386
            Writes data to an I2C device
387
            <bus> the bus index
388
            <slave> the slave address
389
            <addr> the start address on the I2C device
390
            <db1> ... <dbN> the data in single bytes, seperated by whitespaces,
391
                eg. 0x37 0x56 0x45 0x12
392
        """
393
        bus = self._hexint(bus)
394
        slave = self._hexint(slave)
395
        addr = self._hexint(addr)
396
        data = []
397
        for arg in args:
398
            data.append(self._hexint(arg))
399
        raise NotImplementedError
400
 
401
    @command
402
    def readusbconsole(self, size, outtype):
403
        """
404
            Reads data from the USB console.
405
            <size>: the number of bytes to read
406
            <outtype>: defines how to output the result:
407
                'file': writes the result to file <file>
408
                'printstring': writes the result as string to the console window
409
                'printhex': writes the result in hexedit notation to the console window
410
            <file>: the file to write the result to, can be omitted
411
                if <outtype> is not 'file'
412
        """
413
        size = self._hexint(size)
414
        raise NotImplementedError
415
 
416
 
417
    @command
418
    def writeusbconsole_file(self, file, offset=0, length=None):
419
        """
420
            Writes the file <file> to the USB console.
421
            Optional params <offset> <length>: specify the range in <file> to write
422
        """
423
        # We don't care about file here, this is done when opening it
424
        offset = self._hexint(offset)
425
        length = self._hexint(length)
426
        raise NotImplementedError
427
 
428
    @command
429
    def writeusbconsole_direct(self, *args):
430
        """
431
            Writes the strings <db1> ... <dbN> to the USB console."
432
        """
433
        raise NotImplementedError
434
 
435
    @command
436
    def readdevconsole(self, bitmask, size, outtype, file=None):
437
        """
438
            Reads data from one or more of the device's consoles.
439
            <bitmask>: the bitmask of the consoles to read from
440
            <size>: the number of bytes to read
441
            <outtype>: defines how to output the result:
442
                'file': writes the result to file <file>
443
                'printstring': writes the result as string to the console window
444
                'printhex': writes the result in hexedit notation to the console window
445
            <file>: the file to write the result to, can be omitted
446
                if <outtype> is not 'file'
447
        """
448
        bitmask = self._hexint(bitmask)
449
        size = self._hexint(size)
450
        outtype = self._strcheck(['file', 'printstring', 'printhex'])
451
        raise NotImplementedError
452
 
453
    @command
454
    def writedevconsole_file(self, bitmask, file, offset=0, length=None):
455
        """
456
            Writes the file <file> to the device consoles specified by <bitmask>
457
            Optional params <offset> <length>: specify the range in <file> to write
458
        """
459
        bitmask = self._hexint(bitmask)
460
        # We don't care about file here, this is done when opening it
461
        offset = self._hexint(offset)
462
        length = self._hexint(length)
463
        raise NotImplementedError
464
 
465
    @command
466
    def writedevconsole_direct(self, bitmask, *args):
467
        """
468
            Writes the integers <db1> ... <dbN> to the device consoles specified
469
            by <bitmask>
470
        """
471
        bitmask = self._hexint(bitmask)
472
        data = []
473
        for arg in args:
474
            data.append(self._hexint(arg))
475
        raise NotImplementedError
476
 
477
    @command
478
    def flushconsolebuffers(self, bitmask):
479
        """
480
            flushes one or more of the device consoles' buffers.
481
            <bitmask>: the bitmask of the consoles to be flushed
482
        """
483
        bitmask = self._hexint(bitmask)
484
        raise NotImplementedError
485
 
486
    @command
487
    def getprocinfo(self):
488
        """
489
            Fetches data on the currently running processes
490
            ATTENTION: this function will be print the information to the console window.
491
                If several threads are running this might overflow the window,
492
                causing not everything to be shown.
493
        """
494
        raise NotImplementedError
495
 
496
    @command
497
    def lockscheduler(self):
498
        """
499
            Locks (freezes) the scheduler
500
        """
501
        raise NotImplementedError
502
 
503
    @command
504
    def unlockscheduler(self):
505
        """
506
            Unlocks (unfreezes) the scheduler
507
        """
508
        raise NotImplementedError
509
 
510
    @command
511
    def suspendthread(self, threadid):
512
        """
513
            Suspends/resumes the thread with thread ID <threadid>
514
        """
515
        threadid = self._hexint(threadid)
516
        raise NotImplementedError
517
 
518
    @command
519
    def resumethread(self, threadid):
520
        """
521
            Resumes the thread with thread ID <threadid>
522
        """
523
        threadid = self._hexint(threadid)
524
        raise NotImplementedError
525
 
526
    @command
527
    def killthread(self, threadid):
528
        """
529
            Kills the thread with thread ID <threadid>
530
        """
531
        threadid = self._hexint(threadid)
532
        raise NotImplementedError
533
 
534
    @command
535
    def createthread(self, nameptr, entrypoint, stackptr, stacksize, threadtype, priority, state):
536
        """
537
            Creates a new thread and returns its thread ID
538
            <namepointer> a pointer to the thread's name
539
            <entrypoint> a pointer to the entrypoint of the thread
540
            <stackpointer> a pointer to the stack of the thread
541
            <stacksize> the size of the thread's stack
542
            <type> the thread type, vaild are: 0 => user thread, 1 => system thread
543
            <priority> the priority of the thread, from 1 to 255
544
            <state> the thread's initial state, valid are: 1 => ready, 0 => suspended
545
        """
546
        nameptr = self._hexint(nameptr)
547
        entrypoint = self._hexint(entrypoint)
548
        stackpointer = self._hexint(stackpointer)
549
        stacksize = self._hexint(stacksize)
550
        priority = self._hexint(priority)
551
        self.embios.createthread(nameptr, entrypoint, stackptr, stacksize, type, priority, state)
172 farthen 552
 
553
    @command
554
    def run(self, filename):
555
        """
556
            Uploads the emBIOS application to an address in the user memory
557
            and executes it
558
        """
559
        try:
560
            f = open(filename, "rb")
561
        except IOError:
562
            raise ArgumentError("File not readable. Does it exist?")
563
        data = self.embios.getusermemrange()
564
        addr = data.lower
565
        maxsize = data.upper - data.lower
566
        filesize = os.path.getsize(filename)
567
        if filesize > maxsize:
568
            raise ArgumentError("The file is too big, it doesn't fit into the user memory.")
569
        self.logger.info("Uploading application to "+hex(addr)+" - "+hex(addr+filesize)+"\n")
570
        self.embios.write(addr, f.read())
571
        self.execute(addr)
171 farthen 572
 
573
    @command
172 farthen 574
    def execute(self, addr):
171 farthen 575
        """
576
            Executes the emBIOS application at <address>.
577
        """
172 farthen 578
        addr = self._hexint(addr)
579
        self.logger.info("Starting emBIOS app at "+hex(addr)+"\n")
580
        self.embios.execimage(addr)
171 farthen 581
 
582
    @command
583
    def readrawbootflash(self, addr_flash, addr_mem, size):
584
        """
585
            Reads <size> bytes from bootflash to memory.
586
            <addr_bootflsh>: the address in bootflash to read from
587
            <addr_mem>: the address in memory to copy the data to
588
        """
589
        addr_flash = self._hexint(addr_flash)
590
        addr_mem = self._hexint(addr_mem)
591
        size = self._hexint(size)
592
        raise NotImplementedError
593
 
594
    @command
595
    def writerawbootflash(self, addr_flash, addr_mem, size):
596
        """
597
            Writes <size> bytes from memory to bootflash.
598
            ATTENTION: Don't call this unless you really know what you're doing!
599
            This may BRICK your device (unless it has a good recovery option)
600
            <addr_mem>: the address in memory to copy the data from
601
            <addr_bootflsh>: the address in bootflash to write to
602
        """
603
        addr_flash = self._hexint(addr_flash)
604
        addr_mem = self._hexint(addr_mem)
605
        size = self._hexint(size)
606
        raise NotImplementedError
607
 
608
    @command
609
    def flushcaches(self):
610
        """
611
            Flushes the CPUs data and instruction caches.
612
        """
172 farthen 613
        self.logger.info("Flushing CPU data and instruction caches...")
614
        self.embios.flushcaches()
615
        self.logger.info("done\n")
64 benedikt93 616
 
171 farthen 617
    @command
618
    def aesencrypt(self, addr, size, keyindex):
619
        """
172 farthen 620
            Encrypts a buffer using a hardware key
171 farthen 621
        """
622
        addr = self._hexint(addr)
623
        size = self._hexint(size)
624
        keyindex = self._hexint(keyindex)
625
        self.embios.aesencrypt(addr, size, keyindex)
82 benedikt93 626
 
171 farthen 627
    @command
628
    def aesdecrypt(self, addr, size, keyindex):
629
        """
172 farthen 630
            Decrypts a buffer using a hardware key
171 farthen 631
        """
632
        addr = self._hexint(addr)
633
        size = self._hexint(size)
634
        keyindex = self._hexint(keyindex)
635
        self.embios.aesdecrypt(addr, size, keyindex)
172 farthen 636
 
637
    @command
638
    def hmac_sha1(self, addr, size, destination):
639
        """
640
            Generates a HMAC-SHA1 hash of the buffer and saves it to 'destination'
641
        """
642
        addr = self._hexint(addr)
643
        size = self._hexint(size)
644
        destination = self._hexint(destination)
645
        sha1size = 0x14
646
        self.logger.info("Generating hmac-sha1 hash from the buffer at "+hex(addr)+" with the size "+hex(size)+
647
                         " and saving it to "+hex(destination)+" - "+hex(destination+sha1size)+"...")
648
        self.embios.hmac_sha1(addr, size, destination)
649
        self.logger.info("done\n")
650
        data = self.embios.readmem(destination, sha1size)
651
        hash = ord(data)
652
        self.logger.info("The generated hash is "+hex(hash))
64 benedikt93 653
 
171 farthen 654
if __name__ == "__main__":
655
    if len(sys.argv) < 2:
656
        usage("No command specified")
657
    interface = Commandline()
658
    interface._parsecommand(sys.argv[1], sys.argv[2:])