def flush(self): """Force any in-progress transactions to be completed. This should be called when you know the USB analyzer is finished outputting data, such as when a non-USBIO line appears in the log. """ if self.current.dir: self.eventQueue.put(self.current) self.current = Types.Transaction()
def beginUrb(self, pipe): """Simulate a new URB being created on the supplied pipe. This begins a Down transaction and makes it pending and current. """ t = Types.Transaction() t.dir = 'Down' t.dev, t.endpt = pipe t.timestamp = self.timestamp t.frame = parseInt(self._frameAttrs, 'frameNumber') t.status = 0 self.pipes[pipe] = t self.pending[pipe] = t self.current = t
def parse(self, line): self.lineNumber += 1 tokens = line.split() try: # Extract the time, convert to seconds nanotime = int(tokens[0]) if not self.epoch: self.epoch = nanotime timestamp = (nanotime - self.epoch) / 1000000000.0 # Detect the start- or end- prefix name = tokens[1] if name.startswith("begin-"): name = name.split('-', 1)[1] dirs = ('Down',) elif name.startswith("end-"): name = name.split('-', 1)[1] dirs = ('Up',) else: dirs = ('Down', 'Up') # Generate an 'endpoint' for the event name try: endpoint = self.nameEndpoints[name] except KeyError: endpoint = self.nextEp self.nameEndpoints[name] = endpoint self.nextEp = endpoint + 1 for dir in dirs: trans = Types.Transaction() trans.dir = dir trans.timestamp = timestamp trans.lineNumber = self.lineNumber trans.endpt = endpoint trans.dev = 0 trans.status = 0 trans.datalen = 0x1000 trans.appendDecoded(" ".join(tokens[1:])) self.eventQueue.put(trans) except: print "Error on line %d:" % self.lineNumber traceback.print_exc()
def flipUrb(self, pipe): """Begin the Up phase on a particular pipe. This completes the Down transaction, and makes an Up current (but not pending) """ del self.pending[pipe] down = self.pipes[pipe] self.eventQueue.put(down) up = Types.Transaction() up.dir = 'Up' up.dev, up.endpt = pipe # Up and Down transactions share setup data, if applicable if down.hasSetupData(): up.data = down.data[:8] self.pipes[pipe] = up self.current = up
def parse(self, line, timestamp=None): self.lineNumber += 1 tokens = line.split() iso = 0 try: # Do a small stupid sanity check if this is a correct usbmon log line try: if len(tokens) < 4: return if not(int(tokens[0],16) and int(tokens[1]) and (tokens[2] in ('S', 'C', 'E'))): return except: print "Error on line %d:" % self.lineNumber return # Copied log file format description of the usbmon kernel # facility You can find the original manual including how # to use usbmon in your kernel sources: <linux # sources>/Documentation/usb/usbmon.txt # # Copied text starts here: # Any text format data consists of a stream of events, # such as URB submission, URB callback, submission # error. Every event is a text line, which consists of # whitespace separated words. The number or position of # words may depend on the event type, but there is a set # of words, common for all types. # Here is the list of words, from left to right: # - URB Tag. This is used to identify URBs, and is # normally an in-kernel address of the URB structure in # hexadecimal, but can be a sequence number or any other # unique string, within reason. self.trans.lineNumber = self.lineNumber # TODO Usbmon's timestamps can wrap. Annoyingly, they can wrap # at either every 4096 seconds or (about) every 4296 seconds, see # bugzilla.redhat.com/show_bug.cgi?id=574024 Let's wait for some # feedback on that bugreport before adding (a possibly trivial) # way to handle that. # - Timestamp in microseconds, a decimal number. The # timestamp's resolution depends on available clock, and # so it can be much worse than a microsecond (if the # implementation uses jiffies, for example). # Extract the time, convert to seconds microtime = int(tokens[1]) if not self.epoch: self.epoch = microtime timestamp = (microtime - self.epoch) / 1000000.0 self.trans.timestamp = timestamp # - Event Type. This type refers to the format of the # event, not URB type. Available types are: S - # submission, C - callback, E - submission error. if tokens[2] == 'S': self.trans.dir = 'Down' else: self.trans.dir = 'Up' # - "Address" word (formerly a "pipe"). It consists of # four fields, separated by colons: URB type and # direction, Bus number, Device address, Endpoint # number. Type and direction are encoded with two bytes # in the following manner: # # Ci Co Control input and output # Zi Zo Isochronous input and output # Ii Io Interrupt input and output # Bi Bo Bulk input and output # # Bus number, Device address, and Endpoint are decimal # numbers, but they may have leading zeros, for the sake # of human readers. # # Note that older kernels seem to omit the bus number field. # We can parse either format. pipe = tokens[3].split(':') self.trans.dev = int(pipe[-2]) self.trans.endpt = int(pipe[-1]) if pipe[0][1] == 'i' and self.trans.endpt != 0: # Input endpoint self.trans.endpt |= 0x80 if len(pipe) >= 4: self.trans.dev += int(pipe[-3]) * 1000 # - URB Status word. This is either a letter, or several # numbers separated by colons: URB status, interval, # start frame, and error count. Unlike the "address" # word, all fields save the status are # optional. Interval is printed only for interrupt and # isochronous URBs. Start frame is printed only for # isochronous URBs. Error count is printed only for # isochronous callback events. # # The status field is a decimal number, sometimes # negative, which represents a "status" field of the # URB. This field makes no sense for submissions, but is # present anyway to help scripts with parsing. When an # error occurs, the field contains the error code. # # In case of a submission of a Control packet, this # field contains a Setup Tag instead of an group of # numbers. It is easy to tell whether the Setup Tag is # present because it is never a number. Thus if scripts # find a set of numbers in this word, they proceed to # read Data Length (except for isochronous URBs). If # they find something else, like a letter, they read the # setup packet before reading the Data Length or # isochronous descriptors. # # - Setup packet, if present, consists of 5 words: one of # each for bmRequestType, bRequest, wValue, wIndex, # wLength, as specified by the USB Specification 2.0. # These words are safe to decode if Setup Tag was # 's'. Otherwise, the setup packet was present, but not # captured, and the fields contain filler. # # - Number of isochronous frame descriptors and # descriptors themselves. If an Isochronous transfer # event has a set of descriptors, a total number of them # in an URB is printed first, then a word per descriptor, # up to a total of 5. The word consists of 3 # colon-separated decimal numbers for status, offset, and # length respectively. For submissions, initial length is # reported. For callbacks, actual length is reported. if tokens[4] in ('s'): # This is a setup packet # Example data stage: 23 01 0010 0002 0040 self.trans.status = 0 data = ''.join(tokens[5:7]) # use VMX's byte ordering for wValue, wIndex and wLength data = ''.join((data, tokens[7][2:4], tokens[7][0:2])) data = ''.join((data, tokens[8][2:4], tokens[8][0:2])) data = ''.join((data, tokens[9][2:4], tokens[9][0:2])) self.trans.appendHexData(data) # save the setup data to prepend it to the setup packet data stage self.setupData = data else: status_word = tokens[4].split(':') self.trans.status = int(status_word[0]) # check if this is a Callback (the 'Up' part of a transaction) # on a CONTROL endpoint and prepend its Submission's (ie, its # 'Down' part) setup packet, just as is done in the VMX logs. if self.setupData and self.trans.endpt == 0 and \ self.trans.dir == 'Up': self.trans.appendHexData(self.setupData) self.setupData = None # - Data Length. For submissions, this is the requested # length. For callbacks, this is the actual length. if len(tokens) >= 7: if not pipe[0][0] == 'Z': self.trans.datalen = int(tokens[5]) # The isochronous stuff is rather messy ... and probably # fails with the legacy format. It also assumes input # direction. elif self.trans.dir == 'Down' and len(tokens) >= 8: # skip two tokens: # - number of isochronous frame descriptors # - one descriptor self.trans.datalen = int(tokens[7]) iso = 2 elif self.trans.dir == 'Up': # The number of isochronous frame descriptors doesn't # need to equal the number of following descriptors so # we search for '=' and use the preceding token try: equal_sign = tokens.index('=') if equal_sign > 6: self.trans.datalen = int(tokens[equal_sign - 1]) iso = equal_sign - 6 except: pass # - Data tag. The usbmon may not always capture data, even # if length is nonzero. The data words are present only # if this tag is '='. # - Data words follow, in big endian hexadecimal # format. Notice that they are not machine words, but # really just a byte stream split into words to make it # easier to read. Thus, the last word may contain from # one to four bytes. The length of collected data is # limited and can be less than the data length report in # Data Length word. if tokens[6 + iso] in ('='): self.trans.appendHexData(''.join(tokens[7 + iso:])) self.eventQueue.put(self.trans) self.trans = Types.Transaction() # End of copied usbmon description text # End of log file parsing except: print "Error on line %d:" % self.lineNumber traceback.print_exc()
def __init__(self, eventQueue): self.epoch = None self.trans = Types.Transaction() self.setupData = None self.eventQueue = eventQueue
def __init__(self, eventQueue): self.current = Types.Transaction() self.eventQueue = eventQueue
def __init__(self, completed): self.epoch = None self.trans = Types.Transaction() self.trans.frame = 0 self.setupData = None self.completed = completed
def __init__(self, completed): self.current = Types.Transaction() self.completed = completed