From 7d8409c981098d08296da42190ac6de34908cb21 Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Fri, 28 Apr 2023 15:22:33 -0700 Subject: [PATCH 1/6] ps2: Added support for host->device communication Detects if host or device is initiaing communication Outputs byte for stacked decoder Outputs human-readable "binary" for logging --- decoders/ps2/__init__.py | 7 +- decoders/ps2/pd.py | 167 ++++++++++++++++++++++++--------------- 2 files changed, 109 insertions(+), 65 deletions(-) mode change 100644 => 100755 decoders/ps2/__init__.py mode change 100644 => 100755 decoders/ps2/pd.py diff --git a/decoders/ps2/__init__.py b/decoders/ps2/__init__.py old mode 100644 new mode 100755 index 75c47687..d6194dd8 --- a/decoders/ps2/__init__.py +++ b/decoders/ps2/__init__.py @@ -2,6 +2,7 @@ ## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2016 Daniel Schulte +## Copyright (C) 2023 Marshal Horn ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -18,9 +19,11 @@ ## ''' -This protocol decoder can decode PS/2 device -> host communication. +This protocol decoder can decode PS/2 device -> host communication \ +and host -> device communication. -Host -> device communication is currently not supported. +To interpret the data, please stack the appropriate keyboard or mouse \ +decoder ''' from .pd import Decoder diff --git a/decoders/ps2/pd.py b/decoders/ps2/pd.py old mode 100644 new mode 100755 index a9d0a986..ec0b2720 --- a/decoders/ps2/pd.py +++ b/decoders/ps2/pd.py @@ -2,6 +2,7 @@ ## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2016 Daniel Schulte +## Copyright (C) 2023 Marshal Horn ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -18,22 +19,33 @@ ## import sigrokdecode as srd -from collections import namedtuple class Ann: - BIT, START, STOP, PARITY_OK, PARITY_ERR, DATA, WORD = range(7) + BIT, START, WORD, PARITY_OK, PARITY_ERR, STOP, ACK, NACK = range(8) + +class Bit: + def __init__(self, val, ss, es): + self.val = val + self.ss = ss + self.es = es + +class Ps2Packet: + def __init__(self, val, host=False, pok=False, ack=False): + self.val = val #byte value + self.host = host #Host transmissions + self.pok = pok #Parity ok + self.ack = ack #Acknowlege ok for host transmission. -Bit = namedtuple('Bit', 'val ss es') class Decoder(srd.Decoder): api_version = 3 id = 'ps2' name = 'PS/2' longname = 'PS/2' - desc = 'PS/2 keyboard/mouse interface.' + desc = 'PS/2 packet interface used by PC keyboards and mice' license = 'gplv2+' inputs = ['logic'] - outputs = [] + outputs = ['ps2_packet'] tags = ['PC'] channels = ( {'id': 'clk', 'name': 'Clock', 'desc': 'Clock line'}, @@ -42,15 +54,22 @@ class Decoder(srd.Decoder): annotations = ( ('bit', 'Bit'), ('start-bit', 'Start bit'), - ('stop-bit', 'Stop bit'), + ('word', 'Word'), ('parity-ok', 'Parity OK bit'), ('parity-err', 'Parity error bit'), - ('data-bit', 'Data bit'), + ('stop-bit', 'Stop bit'), + ('ack', 'Acknowledge'), + ('nack', 'Not Acknowledge'), + ('start-bit', 'Start bit'), ('word', 'Word'), + ('parity-ok', 'Parity OK bit'), + ('parity-err', 'Parity error bit'), + ('stop-bit', 'Stop bit'), ) annotation_rows = ( ('bits', 'Bits', (0,)), - ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), + ('fields', 'Device', (1,2,3,4,5,6,7,)), + ('host', 'Host', (8,9,10,11,12)), ) def __init__(self): @@ -62,65 +81,87 @@ def reset(self): def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) - - def putb(self, bit, ann_idx): - b = self.bits[bit] - self.put(b.ss, b.es, self.out_ann, [ann_idx, [str(b.val)]]) - - def putx(self, bit, ann): + self.out_py = self.register(srd.OUTPUT_PYTHON) + + def metadata(self,key,value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def get_bits(self, n, edge:'r', timeout=100e-6): + max_period = int(timeout * self.samplerate) + 1 + for i in range(n): + _, dat = self.wait([{0:edge},{'skip':max_period}]) + if not self.matched[0]: + break #Timed out + self.bits.append(Bit(dat, self.samplenum, self.samplenum+max_period)) + if i>0: #Fix the ending period + self.bits[i-1].es = self.samplenum + if len(self.bits) == n: + self.wait([{0:'r'},{'skip':max_period}]) + self.bits[-1].es = self.samplenum + self.bitcount = len(self.bits) + + def putx(self, bit, ann, host=False): + if host: + ann[0] += 7 #host annotation offset self.put(self.bits[bit].ss, self.bits[bit].es, self.out_ann, ann) - def handle_bits(self, datapin): - # Ignore non start condition bits (useful during keyboard init). - if self.bitcount == 0 and datapin == 1: - return - - # Store individual bits and their start/end samplenumbers. - self.bits.append(Bit(datapin, self.samplenum, self.samplenum)) - - # Fix up end sample numbers of the bits. - if self.bitcount > 0: - b = self.bits[self.bitcount - 1] - self.bits[self.bitcount - 1] = Bit(b.val, b.ss, self.samplenum) - if self.bitcount == 11: - self.bitwidth = self.bits[1].es - self.bits[2].es - b = self.bits[-1] - self.bits[-1] = Bit(b.val, b.ss, b.es + self.bitwidth) - - # Find all 11 bits. Start + 8 data + odd parity + stop. - if self.bitcount < 11: - self.bitcount += 1 - return - - # Extract data word. - word = 0 - for i in range(8): - word |= (self.bits[i + 1].val << i) + def handle_bits(self, host=False): + # Annotate individual bits + for b in self.bits: + self.put(b.ss, b.es, self.out_ann, [Ann.BIT, [str(b.val)]]) + + packet = None + # Annotate start bit + if self.bitcount > 8: + self.putx(0, [Ann.START, ['Start bit', 'Start', 'S']], host) + # Annotate the data word + word = 0 + for i in range(8): + word |= (self.bits[i + 1].val << i) + self.put(self.bits[1].ss, self.bits[8].es, self.out_ann, + [Ann.WORD+7 if host else Ann.WORD, + ['Data: %02x' % word, 'D: %02x' % word, '%02x' % word]]) + packet = Ps2Packet(val = word, host = host) # Calculate parity. - parity_ok = (bin(word).count('1') + self.bits[9].val) % 2 == 1 - - # Emit annotations. - for i in range(11): - self.putb(i, Ann.BIT) - self.putx(0, [Ann.START, ['Start bit', 'Start', 'S']]) - self.put(self.bits[1].ss, self.bits[8].es, self.out_ann, [Ann.WORD, - ['Data: %02x' % word, 'D: %02x' % word, '%02x' % word]]) - if parity_ok: - self.putx(9, [Ann.PARITY_OK, ['Parity OK', 'Par OK', 'P']]) - else: - self.putx(9, [Ann.PARITY_ERR, ['Parity error', 'Par err', 'PE']]) - self.putx(10, [Ann.STOP, ['Stop bit', 'Stop', 'St', 'T']]) - - self.bits, self.bitcount = [], 0 + if self.bitcount > 9: + parity_ok = 0 + for bit in self.bits[1:10]: + parity_ok ^= bit.val + if bool(parity_ok): + self.putx(9, [Ann.PARITY_OK, ['Parity OK', 'Par OK', 'P']], host) + packet.pok = True #Defaults to false in case packet was interrupted + else: + self.putx(9, [Ann.PARITY_ERR, ['Parity error', 'Par err', 'PE']], host) + + # Annotate stop bit + if self.bitcount > 10: + self.putx(10, [Ann.STOP+7 if host else Ann.STOP, ['Stop bit', 'Stop', 'St', 'T']]) + # Annotate ACK + if host and self.bitcount > 11: + if self.bits[11].val == 0: + self.putx(11, [Ann.ACK, ['Acknowledge', 'Ack', 'A']]) + else: + self.putx(11, [Ann.NACK, ['Not Acknowledge', 'Nack', 'N']]) + packet.ack = not bool(self.bits[11].val) + + if(packet): + self.put(self.bits[0].ss, self.bits[-1].ss, self.out_py,packet) + self.reset() + def decode(self): + if not self.samplerate: + raise SamplerateError("Cannot decode without samplerate") while True: - # Sample data bits on the falling clock edge (assume the device - # is the transmitter). Expect the data byte transmission to end - # at the rising clock edge. Cope with the absence of host activity. - _, data_pin = self.wait({0: 'f'}) - self.handle_bits(data_pin) - if self.bitcount == 1 + 8 + 1 + 1: - _, data_pin = self.wait({0: 'r'}) - self.handle_bits(data_pin) + # Falling edge of data indicates start condition + clk, dat = self.wait({1: 'l'}) + host = not bool(clk) + if host: + # Host emits bits on rising clk edge + self.get_bits(12, 'r') + else: + # Client emits data on falling edge + self.get_bits(11, 'f') + self.handle_bits(host=host) From be0442c9f01e5971d1121c47227e522ed4b28fb5 Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Fri, 28 Apr 2023 15:32:34 -0700 Subject: [PATCH 2/6] ps2: Added mouse and keyboard stacked decoders Mouse decoder interprets the 3-byte packets for movement and clicking, but ignores the configuration commands from the host. Keyboard decoder interprets press and release messages for each key but does not keep track of state (capslock, shift, etc). --- decoders/ps2_keyboard/__init__.py | 25 ++++++ decoders/ps2_keyboard/pd.py | 93 ++++++++++++++++++++++ decoders/ps2_keyboard/sc.py | 99 ++++++++++++++++++++++++ decoders/ps2_mouse/__init__.py | 25 ++++++ decoders/ps2_mouse/pd.py | 124 ++++++++++++++++++++++++++++++ 5 files changed, 366 insertions(+) create mode 100755 decoders/ps2_keyboard/__init__.py create mode 100755 decoders/ps2_keyboard/pd.py create mode 100644 decoders/ps2_keyboard/sc.py create mode 100755 decoders/ps2_mouse/__init__.py create mode 100755 decoders/ps2_mouse/pd.py diff --git a/decoders/ps2_keyboard/__init__.py b/decoders/ps2_keyboard/__init__.py new file mode 100755 index 00000000..a9d499a6 --- /dev/null +++ b/decoders/ps2_keyboard/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2023 Marshal Horn +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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 . +## + +''' +This protocol decoder can decode PS/2 keyboard commands. +It should be stacked on the PS/2 packet decoder. +''' + +from .pd import Decoder diff --git a/decoders/ps2_keyboard/pd.py b/decoders/ps2_keyboard/pd.py new file mode 100755 index 00000000..775f01f7 --- /dev/null +++ b/decoders/ps2_keyboard/pd.py @@ -0,0 +1,93 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2023 Marshal Horn +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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 . +## + +import sigrokdecode as srd +from .sc import key_decode + +class Ps2Packet: + def __init__(self, val, host=False, pok=False, ack=False): + self.val = val #byte value + self.host = host #Host transmissions + self.pok = pok #Parity ok + self.ack = ack #Acknowlege ok for host transmission. + +class Ann: + PRESS,RELEASE,ACK = range(3) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'keyboard' + name = 'PS/2 Keyboard' + longname = 'PS/2 Keyboard' + desc = 'PS/2 keyboard interface.' + license = 'gplv2+' + inputs = ['ps2_packet'] + outputs = [] + tags = ['PC'] + binary = ( + ('Keys', 'Key presses'), + ) + annotations = ( + ('Press', 'Key pressed'), + ('Release', 'Key released'), + ('Ack', 'Acknowledge'), + ) + annotation_rows = ( + ('keys', 'key presses and releases',(0,1,2)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.sw = 0 #for switch statement + self.ann = Ann.PRESS #defualt to keypress + self.extended = False + + def start(self): + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self,startsample,endsample,data): + if data.host: + # Ignore host commands or interrupted keycodes + self.reset() + return + if self.sw < 1: + self.ss = startsample + self.sw = 1 + if self.sw < 2: + if data.val == 0xF0: #Break code + self.ann = Ann.RELEASE + return + elif data.val == 0xE0: #Extended character + self.extended = True + return + elif data.val == 0xFA: #Acknowledge code + c = ['Acknowledge','ACK'] + self.ann = Ann.ACK + self.sw = 4 + if self.sw < 3: + c = key_decode(data.val, self.extended) + + self.put(self.ss,endsample,self.out_ann,[self.ann,c]) + if self.ann == Ann.PRESS: + self.put(self.ss,endsample,self.out_binary,[0,c[-1].encode('UTF-8')]) + self.reset() + diff --git a/decoders/ps2_keyboard/sc.py b/decoders/ps2_keyboard/sc.py new file mode 100644 index 00000000..96f76c16 --- /dev/null +++ b/decoders/ps2_keyboard/sc.py @@ -0,0 +1,99 @@ +#A list of scancodes for the PS2 keyboard +ext = { + 0x1F:'L Sup', + 0x14:'R Ctrl', + 0x27:'R Sup', + 0x11:'R Alt', + 0x2F:'Menu', + 0x12:'PrtScr', + 0x7C:'SysRq', + 0x70:'Insert', + 0x6C:'Home', + 0x7D:'Pg Up', + 0x71:'Delete', + 0x69:'End', + 0x7A:'Pg Dn', + 0x75:['Up arrow','^'], + 0x6B:['Left arrow','Left','<-'], + 0x74:['Right arrow','Right','->'], + 0x72:['Down arrow','Down','v'], + 0x4A:['KP /','/'], + 0x5A:['KP Ent','\n'], +} + +std = { + 0x1C:'A', + 0x32:'B', + 0x21:'C', + 0x23:'D', + 0x24:'E', + 0x2B:'F', + 0x34:'G', + 0x33:'H', + 0x43:'I', + 0x3B:'J', + 0x42:'K', + 0x4B:'L', + 0x3A:'M', + 0x31:'N', + 0x44:'O', + 0x4D:'P', + 0x15:'Q', + 0x2D:'R', + 0x1B:'S', + 0x2C:'T', + 0x3C:'U', + 0x2A:'V', + 0x1D:'W', + 0x22:'X', + 0x35:'Y', + 0x1A:'Z', + 0x45:'0)', + 0x16:'1!', + 0x1E:'2@', + 0x26:'3#', + 0x25:'4$', + 0x2E:'5%', + 0x36:'6^', + 0x3D:'7&', + 0x3E:'8*', + 0x46:'9(', + 0x0E:'`~', + 0x4E:'-_', + 0x55:'=+', + 0x5D:'\|', + 0x66:'Backsp', + 0x29:['Space',' '], + 0x0D:['Tab','\t'], + 0x58:'CapsLk', + 0x12:'L Shft', + 0x14:'L Ctrl', + 0x11:'L Alt', + 0x59:'R Shft', + 0x5A:['Enter','\n'], + 0x76:'Esc', + 0x5:'F1', + 0x6:'F2', + 0x4:'F3', + 0x0C:'F4', + 0x3:'F5', + 0x0B:'F6', + 0x83:'F7', + 0x0A:'F8', + 0x1:'F9', + 0x9:'F10', + 0x78:'F11', + 0x7:'F12', + 0x7E:'ScrLck', +} + + +def key_decode(code, extended): + try: + c = ext[code] if extended else std[code] + except KeyError: + fs = '[E0%0X]' if extended else '[%0X]' + c = fs % code + if not isinstance(0,list): + c = [c] + return c diff --git a/decoders/ps2_mouse/__init__.py b/decoders/ps2_mouse/__init__.py new file mode 100755 index 00000000..b43a1684 --- /dev/null +++ b/decoders/ps2_mouse/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2023 Marshal Horn +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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 . +## + +''' +This protocol decoder can decode PS/2 mouse commands. +It should be stacked on the PS/2 packet decoder. +''' + +from .pd import Decoder diff --git a/decoders/ps2_mouse/pd.py b/decoders/ps2_mouse/pd.py new file mode 100755 index 00000000..9ebcf184 --- /dev/null +++ b/decoders/ps2_mouse/pd.py @@ -0,0 +1,124 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2023 Marshal Horn +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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 . +## + +import sigrokdecode as srd + +class Ps2Packet: + def __init__(self, val, host=False, pok=False, ack=False): + self.val = val #byte value + self.host = host #Host transmissions + self.pok = pok #Parity ok + self.ack = ack #Acknowlege ok for host transmission. + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mouse' + name = 'PS/2 Mouse' + longname = 'PS/2 Mouse' + desc = 'PS/2 mouse interface.' + license = 'gplv2+' + inputs = ['ps2_packet'] + outputs = [] + tags = ['PC'] + binary = ( + ('bytes', 'Bytes without explanation'), + ('movement', 'Explanation of mouse movement and clicks'), + ) + annotations = ( + ('Movement', 'Mouse movement packets'), + ) + annotation_rows = ( + ('mov', 'Mouse Movement',(0,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.packets = [] + self.es = 0 + self.ss = 0 + + def start(self): + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self,key,value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def mouse_movement(self): + if len(self.packets) >= 3: + if not self.packets[0].host: + msg = '' + [flags,x,y] = [p.val for p in self.packets[:3]] + if flags & 1: + msg += 'L' + if flags & 2: + msg += 'M' + if flags & 4: + msg += 'R' + if flags & 0x10: + x = x-256 + if flags & 0x20: + y = y-256 + if x != 0: + msg += ' X%+d' % x + if flags & 0x40: + msg += '!!' + if y != 0: + msg += ' Y%+d' % y + if flags & 0x80: + msg += '!!' + if msg == '': + msg = 'No Movement' + ustring = ('\n' + msg).encode('UTF-8') + self.put(self.ss,self.es,self.out_binary, [1,ustring] ) + self.put(self.ss,self.es,self.out_ann, [0,[msg]] ) + + + def print_packets(self): + self.mouse_movement() + tag = "Host: " if self.packets[-1].host else "Mouse:" + octets = ' '.join(["%02X" % x.val for x in self.packets]) + unicode_string = ("\n"+tag+" "+octets).encode('UTF-8') + self.put(self.ss,self.es,self.out_binary, [0,unicode_string] ) + self.reset() + + def mouse_ack(self,ss,es): + self.put(ss,es,self.out_binary, [0,b' ACK'] ) + + def decode(self,startsample,endsample,data): + if len(self.packets) == 0: + self.ss = startsample + elif data.host != self.packets[-1].host: + self.print_packets() + self.ss = startsample #Packets were cleared, need to set startsample again + if data.val == 0xFA and not data.host: + #Special case: acknowledge byte from mouse + self.mouse_ack(startsample,endsample) + self.reset() + return + + self.packets.append(data) + self.es = endsample + #Mouse streaming packets are in 3s + #Timing is not guaranteed because host can hold the clock at any point + if len(self.packets)>2: + self.print_packets() From 1ddb1cdf86a0aaa93757316bbfad4ee534821ffd Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Sat, 29 Apr 2023 08:03:20 -0700 Subject: [PATCH 3/6] ps2: Improved name consistency Improved name consistency to assist in unit tests "mouse" and "keyboard" decoders renamed to "ps2_mouse" and "ps2_keyboard" "ps2_packet" output renamed to simply "ps2" --- decoders/ps2/pd.py | 2 +- decoders/ps2_keyboard/pd.py | 4 ++-- decoders/ps2_mouse/pd.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/decoders/ps2/pd.py b/decoders/ps2/pd.py index ec0b2720..e0b23df2 100755 --- a/decoders/ps2/pd.py +++ b/decoders/ps2/pd.py @@ -45,7 +45,7 @@ class Decoder(srd.Decoder): desc = 'PS/2 packet interface used by PC keyboards and mice' license = 'gplv2+' inputs = ['logic'] - outputs = ['ps2_packet'] + outputs = ['ps2'] tags = ['PC'] channels = ( {'id': 'clk', 'name': 'Clock', 'desc': 'Clock line'}, diff --git a/decoders/ps2_keyboard/pd.py b/decoders/ps2_keyboard/pd.py index 775f01f7..70cae724 100755 --- a/decoders/ps2_keyboard/pd.py +++ b/decoders/ps2_keyboard/pd.py @@ -32,12 +32,12 @@ class Ann: class Decoder(srd.Decoder): api_version = 3 - id = 'keyboard' + id = 'ps2_keyboard' name = 'PS/2 Keyboard' longname = 'PS/2 Keyboard' desc = 'PS/2 keyboard interface.' license = 'gplv2+' - inputs = ['ps2_packet'] + inputs = ['ps2'] outputs = [] tags = ['PC'] binary = ( diff --git a/decoders/ps2_mouse/pd.py b/decoders/ps2_mouse/pd.py index 9ebcf184..0101deab 100755 --- a/decoders/ps2_mouse/pd.py +++ b/decoders/ps2_mouse/pd.py @@ -28,12 +28,12 @@ def __init__(self, val, host=False, pok=False, ack=False): class Decoder(srd.Decoder): api_version = 3 - id = 'mouse' + id = 'ps2_mouse' name = 'PS/2 Mouse' longname = 'PS/2 Mouse' desc = 'PS/2 mouse interface.' license = 'gplv2+' - inputs = ['ps2_packet'] + inputs = ['ps2'] outputs = [] tags = ['PC'] binary = ( From 3b86eae823b090edb4bf44e58c1f3f48af97c172 Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Sat, 29 Apr 2023 11:55:16 -0700 Subject: [PATCH 4/6] ps2: Improved Host request-to-send detection - Changed start-bit to begin on data edge rather than clock edge - Increased timeout to accept borderline slow clock speeds - Changed Host RTS to look for the clock line held low for 100us, rather than a low clock on the falling edge of the data. Some hosts release the clock immidiately after pulling the data low, which may look simultaneous even at 1MHz sample rates. - Added Host RTS annotation - Removed confusing "annotation offset" constant, expanded Ann class with host annotations - Changed to not annotate bits if less than 9 are captured (incomplete word) --- decoders/ps2/pd.py | 55 ++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/decoders/ps2/pd.py b/decoders/ps2/pd.py index e0b23df2..b8a9be1f 100755 --- a/decoders/ps2/pd.py +++ b/decoders/ps2/pd.py @@ -21,7 +21,7 @@ import sigrokdecode as srd class Ann: - BIT, START, WORD, PARITY_OK, PARITY_ERR, STOP, ACK, NACK = range(8) + BIT, START, WORD, PARITY_OK, PARITY_ERR, STOP, ACK, NACK, HREQ, HSTART, HWORD, HPARITY_OK, HPARITY_ERR, HSTOP = range(14) class Bit: def __init__(self, val, ss, es): @@ -60,6 +60,7 @@ class Decoder(srd.Decoder): ('stop-bit', 'Stop bit'), ('ack', 'Acknowledge'), ('nack', 'Not Acknowledge'), + ('req', 'Host request to send'), ('start-bit', 'Start bit'), ('word', 'Word'), ('parity-ok', 'Parity OK bit'), @@ -69,7 +70,7 @@ class Decoder(srd.Decoder): annotation_rows = ( ('bits', 'Bits', (0,)), ('fields', 'Device', (1,2,3,4,5,6,7,)), - ('host', 'Host', (8,9,10,11,12)), + ('host', 'Host', (8,9,10,11,12,13)), ) def __init__(self): @@ -87,40 +88,43 @@ def metadata(self,key,value): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value - def get_bits(self, n, edge:'r', timeout=100e-6): + def get_bits(self, n, edge:'r', timeout=110e-6): max_period = int(timeout * self.samplerate) + 1 - for i in range(n): + _, dat = self.wait([{0:edge},{1:'l'}]) #No timeout for start bit + if not self.matched[1]: + return #No start bit + elif not self.matched[0]: + _, dat = self.wait({0:edge}) #Wait for clock edge + self.bits.append(Bit(dat, self.samplenum, self.samplenum+max_period)) + for i in range(1,n): _, dat = self.wait([{0:edge},{'skip':max_period}]) if not self.matched[0]: break #Timed out self.bits.append(Bit(dat, self.samplenum, self.samplenum+max_period)) - if i>0: #Fix the ending period - self.bits[i-1].es = self.samplenum + #Fix the ending period + self.bits[i-1].es = self.samplenum if len(self.bits) == n: self.wait([{0:'r'},{'skip':max_period}]) self.bits[-1].es = self.samplenum self.bitcount = len(self.bits) - def putx(self, bit, ann, host=False): - if host: - ann[0] += 7 #host annotation offset + def putx(self, bit, ann): self.put(self.bits[bit].ss, self.bits[bit].es, self.out_ann, ann) def handle_bits(self, host=False): - # Annotate individual bits - for b in self.bits: - self.put(b.ss, b.es, self.out_ann, [Ann.BIT, [str(b.val)]]) - packet = None - # Annotate start bit if self.bitcount > 8: - self.putx(0, [Ann.START, ['Start bit', 'Start', 'S']], host) + # Annotate individual bits + for b in self.bits: + self.put(b.ss, b.es, self.out_ann, [Ann.BIT, [str(b.val)]]) + # Annotate start bit + self.putx(0, [Ann.HSTART if host else Ann.START, ['Start bit', 'Start', 'S']]) # Annotate the data word word = 0 for i in range(8): word |= (self.bits[i + 1].val << i) self.put(self.bits[1].ss, self.bits[8].es, self.out_ann, - [Ann.WORD+7 if host else Ann.WORD, + [Ann.HWORD if host else Ann.WORD, ['Data: %02x' % word, 'D: %02x' % word, '%02x' % word]]) packet = Ps2Packet(val = word, host = host) @@ -130,14 +134,14 @@ def handle_bits(self, host=False): for bit in self.bits[1:10]: parity_ok ^= bit.val if bool(parity_ok): - self.putx(9, [Ann.PARITY_OK, ['Parity OK', 'Par OK', 'P']], host) + self.putx(9, [Ann.HPARITY_OK if host else Ann.PARITY_OK, ['Parity OK', 'Par OK', 'P']]) packet.pok = True #Defaults to false in case packet was interrupted else: - self.putx(9, [Ann.PARITY_ERR, ['Parity error', 'Par err', 'PE']], host) + self.putx(9, [Ann.HPARITY_ERR if host else Ann.PARITY_ERR, ['Parity error', 'Par err', 'PE']]) # Annotate stop bit if self.bitcount > 10: - self.putx(10, [Ann.STOP+7 if host else Ann.STOP, ['Stop bit', 'Stop', 'St', 'T']]) + self.putx(10, [Ann.HSTOP if host else Ann.STOP, ['Stop bit', 'Stop', 'St', 'T']]) # Annotate ACK if host and self.bitcount > 11: if self.bits[11].val == 0: @@ -154,13 +158,22 @@ def handle_bits(self, host=False): def decode(self): if not self.samplerate: raise SamplerateError("Cannot decode without samplerate") + max_period = int(100e-6 * self.samplerate) while True: # Falling edge of data indicates start condition - clk, dat = self.wait({1: 'l'}) - host = not bool(clk) + # Clock held for 100us indicates host "request to send" + self.wait([{1: 'f'},{0:'l'}]) + ss = self.samplenum + host = self.matched[1] if host: + # Make sure the clock is held low for at least 100 microseconds + self.wait([{0:'h'},{'skip': max_period}]) + if self.matched[0]: + continue #Probably the trailing edge of a transfer # Host emits bits on rising clk edge self.get_bits(12, 'r') + if self.bitcount > 0: + self.put(ss,self.bits[0].ss,self.out_ann, [Ann.HREQ,['Host RTS', 'HRTS', 'H']]) else: # Client emits data on falling edge self.get_bits(11, 'f') From a984c79dab3bb03af05b911d0afd27f74041ef38 Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Sat, 29 Apr 2023 13:11:30 -0700 Subject: [PATCH 5/6] ps2: Improved rejection of bus errors Host RTS is cleared if data is pulled low within 100us Start bit doesn't have a maxium length requirement, the timer now begins when the client pulls the clock low (after the host has released it) --- decoders/ps2/pd.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/decoders/ps2/pd.py b/decoders/ps2/pd.py index b8a9be1f..29e71a45 100755 --- a/decoders/ps2/pd.py +++ b/decoders/ps2/pd.py @@ -93,9 +93,9 @@ def get_bits(self, n, edge:'r', timeout=110e-6): _, dat = self.wait([{0:edge},{1:'l'}]) #No timeout for start bit if not self.matched[1]: return #No start bit - elif not self.matched[0]: - _, dat = self.wait({0:edge}) #Wait for clock edge self.bits.append(Bit(dat, self.samplenum, self.samplenum+max_period)) + if not self.matched[0]: + self.wait({0:'f'}) #Wait for clock edge from device for i in range(1,n): _, dat = self.wait([{0:edge},{'skip':max_period}]) if not self.matched[0]: @@ -166,10 +166,13 @@ def decode(self): ss = self.samplenum host = self.matched[1] if host: - # Make sure the clock is held low for at least 100 microseconds - self.wait([{0:'h'},{'skip': max_period}]) + # Make sure the clock is held low for at least 100 microseconds before data is pulled down + self.wait([{0:'h'},{'skip': max_period},{1:'l'}]) if self.matched[0]: continue #Probably the trailing edge of a transfer + elif self.matched[2]: #Probably a bus error + self.wait({1:'h'}) #Wait for data to be released + continue # Host emits bits on rising clk edge self.get_bits(12, 'r') if self.bitcount > 0: From 59de0c944917aaf3da89625cd35af80e8784e79c Mon Sep 17 00:00:00 2001 From: Marshal Horn Date: Mon, 1 May 2023 11:26:38 -0700 Subject: [PATCH 6/6] ps2: Explained timeout constant Refactored timeout into a class property instead of a function argument. Now based on minimum clock frequency, rather than maximum clock period --- decoders/ps2/pd.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/decoders/ps2/pd.py b/decoders/ps2/pd.py index 29e71a45..c1ac6246 100755 --- a/decoders/ps2/pd.py +++ b/decoders/ps2/pd.py @@ -75,6 +75,7 @@ class Decoder(srd.Decoder): def __init__(self): self.reset() + self.min_clk_hz = 9e3 #Minimum clock rate for PS/2 is 10kHz, but I want to be lenient def reset(self): self.bits = [] @@ -88,23 +89,22 @@ def metadata(self,key,value): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value - def get_bits(self, n, edge:'r', timeout=110e-6): - max_period = int(timeout * self.samplerate) + 1 + def get_bits(self, n, edge:'r'): _, dat = self.wait([{0:edge},{1:'l'}]) #No timeout for start bit if not self.matched[1]: return #No start bit - self.bits.append(Bit(dat, self.samplenum, self.samplenum+max_period)) + self.bits.append(Bit(dat, self.samplenum, self.samplenum+self.max_period)) if not self.matched[0]: self.wait({0:'f'}) #Wait for clock edge from device for i in range(1,n): - _, dat = self.wait([{0:edge},{'skip':max_period}]) + _, dat = self.wait([{0:edge},{'skip':self.max_period}]) if not self.matched[0]: break #Timed out - self.bits.append(Bit(dat, self.samplenum, self.samplenum+max_period)) + self.bits.append(Bit(dat, self.samplenum, self.samplenum+self.max_period)) #Fix the ending period self.bits[i-1].es = self.samplenum if len(self.bits) == n: - self.wait([{0:'r'},{'skip':max_period}]) + self.wait([{0:'r'},{'skip':self.max_period}]) self.bits[-1].es = self.samplenum self.bitcount = len(self.bits) @@ -156,9 +156,10 @@ def handle_bits(self, host=False): def decode(self): - if not self.samplerate: + if self.samplerate: + self.max_period = int(self.samplerate / self.min_clk_hz)+1 + else: raise SamplerateError("Cannot decode without samplerate") - max_period = int(100e-6 * self.samplerate) while True: # Falling edge of data indicates start condition # Clock held for 100us indicates host "request to send" @@ -167,7 +168,7 @@ def decode(self): host = self.matched[1] if host: # Make sure the clock is held low for at least 100 microseconds before data is pulled down - self.wait([{0:'h'},{'skip': max_period},{1:'l'}]) + self.wait([{0:'h'},{'skip': self.max_period},{1:'l'}]) if self.matched[0]: continue #Probably the trailing edge of a transfer elif self.matched[2]: #Probably a bus error