From: John Tsiombikas Date: Sat, 6 Mar 2021 03:55:08 +0000 (+0200) Subject: plausible i2c sniffer functionality and some more experiments, X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=tv_i2c_hack;a=commitdiff_plain;h=efb5b9c4333f6034595b30d7d549fab2b120d9c9 plausible i2c sniffer functionality and some more experiments, jacked up the clock to 20MHz to have some headroom for the sniffer, but I'll probably drop it down to 3.6 again later, since the sniffer is not that useful. --- diff --git a/Makefile b/Makefile index 86277c5..9d5bcf1 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ OBJCOPY = avr-objcopy warn = -pedantic -Wall -CFLAGS = -Os $(warn) -mmcu=$(mcu_gcc) -DF_CPU=3686400 +CFLAGS = -Os $(warn) -mmcu=$(mcu_gcc) -DF_CPU=20000000 LDFLAGS = -Wl,-Map,$(bin).map -mmcu=$(mcu_gcc) -lprintf_min .PHONY: all diff --git a/main.c b/main.c index aa593da..fa0be2f 100644 --- a/main.c +++ b/main.c @@ -6,13 +6,64 @@ #include #include "serial.h" +#undef USE_PCINT + enum { I2C_IDLE, - I2C_MASTER_SEND, - I2C_MASTER_RECV + I2C_MASTER_WRITE, + I2C_MASTER_READ +}; + +enum { + ST_INVALID = 0, + ST_UNKNOWN = 0xf8, + ST_START = 0x08, + ST_REP_START = 0x10, + ST_SLA_W_ACK = 0x18, + ST_SLA_W_NACK = 0x20, + ST_WR_ACK = 0x28, + ST_WR_NACK = 0x30, + ST_ARBLOST = 0x38, + ST_SLA_R_ACK = 0x40, + ST_SLA_R_NACK = 0x48, + ST_RD_ACK = 0x50, + ST_RD_NACK = 0x58, + ST_MATCH_W = 0x60, + ST_ARBLOST_MATCH = 0x68, + ST_GENMATCH = 0x70, + ST_ARBLOST_GENMATCH = 0x78, + ST_SLAVE_RD_ACK = 0x80, + ST_SLAVE_RD_NACK = 0x88, + ST_SLAVE_GENRD_ACK = 0x90, + ST_SLAVE_GENRD_NACK = 0x98, + ST_SLAVE_STARTSTOP = 0xa0, + ST_SLAVE_SLA_WR_ACK = 0xa8, + ST_SLAVE_ARBLOST_WR_ACK = 0xb0, + ST_SLAVE_WR_ACK = 0xb8, + ST_SLAVE_WR_NACK = 0xc0, + ST_SLAVE_LAST_WR_ACK = 0xc8 +}; + +enum { + EV_DEBUG, + EV_START, + EV_STOP, + EV_SLA_W, + EV_SLA_R, + EV_DATA +}; + +static const char *evname[] = { + "DBG", + "STA", + "STO", + "A+W", + "A+R", + "DAT" }; #define TWINT_BIT (1 << TWINT) +#define TWEA_BIT (1 << TWEA) #define TWSTA_BIT (1 << TWSTA) #define TWSTO_BIT (1 << TWSTO) #define TWEN_BIT (1 << TWEN) @@ -21,6 +72,8 @@ enum { #define I2C_START() (TWCR = TWINT_BIT | TWSTA_BIT | TWEN_BIT | TWIE_BIT) #define I2C_STOP() (TWCR = TWINT_BIT | TWSTO_BIT | TWEN_BIT | TWIE_BIT) #define I2C_WRITE() (TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT) +#define I2C_READ_ACK() (TWCR = TWINT_BIT | TWEA_BIT | TWEN_BIT | TWIE_BIT) +#define I2C_READ_NACK() (TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT) unsigned char i2c_addr, i2c_subaddr; volatile unsigned char i2c_mode; @@ -32,12 +85,16 @@ int i2c_retry_delay; uint16_t evq[EVQ_SIZE]; volatile int evq_wr, evq_rd; -#define LOGNUM(x) \ +#define LOGNUM(x) EVLOG(EV_DEBUG, (x)) + +#define EVLOG(ev, data) \ do { \ - evq[evq_wr] = x; \ - evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); \ - if(evq_wr == evq_rd) { \ - evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); \ + if(ev != EV_DEBUG || dbgmode) { \ + evq[evq_wr] = ((uint16_t)(ev) << 8) | (data); \ + evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); \ + if(evq_wr == evq_rd) { \ + evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); \ + } \ } \ } while(0) @@ -45,16 +102,38 @@ volatile int evq_wr, evq_rd; static char input[64]; static unsigned char inp_cidx; -static unsigned char data[8]; +static unsigned char data[16]; + +static void (*async_func)(void); + +static uint16_t dump_addr; +static int dump_count; +static int dbgmode; +static int snooping; static void proc_cmd(char *input); -void i2c_write(unsigned char addr, unsigned char subaddr, int ndata, unsigned char *data); +static void printstat(void); +static void printdump(void); + +void i2c_write(unsigned char addr, unsigned char subaddr, unsigned char *data, int ndata); +void i2c_read(unsigned char addr, unsigned char subaddr, unsigned char *buf, int size); +void i2c_wait(void); +void i2c_async(void (*donecb)(void)); +void i2c_check_async(void); +void i2c_hold(void); +void i2c_release(void); +void i2c_snoop(int onoff); +#ifndef USE_PCINT +void snoop(void); +#endif int main(void) { + uint16_t val; + /* tri-state everything and disable pullups */ DDRB = 1; /* B0: activity LED */ - DDRC = 0x20; /* SCL output so we can hold the clock low when disabling i2c */ + DDRC = 0; /* I2C pins as inputs */ DDRD = 0; PORTB = 0; PORTC = 0; @@ -62,11 +141,12 @@ int main(void) TWBR = 10; /* 10 with 1x prescaler should make it about 100khz */ TWSR = 0; /* prescaler 1x */ - TWAR = 0x8a; /* let's monitor for commands going to the jungle */ + TWAR = 0; TWAMR = 0; - TWCR = (1 << TWEN) | (1 << TWIE); /* enable i2c, don't ack, enable intr */ + TWCR = 0; /* I2C disabled by default */ i2c_mode = I2C_IDLE; + i2c_snoop(0); /* disable snooping by default */ init_serial(38400); sei(); @@ -74,7 +154,17 @@ int main(void) printf("Starting i2c hack\n"); for(;;) { - //uint16_t val = 0xffff; +#ifndef USE_PCINT + if(snooping) { + snoop(); + if(have_input()) { + i2c_snoop(0); + } + continue; + } +#endif + + i2c_check_async(); if(have_input()) { int c = getchar(); @@ -91,7 +181,7 @@ int main(void) } /* read from queue and send over the serial port */ - /* + val = 0xffff; cli(); if(evq_wr != evq_rd) { val = evq[evq_rd]; @@ -100,85 +190,168 @@ int main(void) sei(); if(val != 0xffff) { - printf("s(%u): %x\n", (unsigned int)(val >> 8), (unsigned int)(val & 0xff)); + unsigned char ev = val >> 8; + if(ev > EV_DATA) { + printf("%02x: %02x\n", ev, val & 0xff); + } else { + printf("%s: %02x\n", evname[ev], val & 0xff); + } } - */ - /* - _delay_us(100); - data[0] = 0x3f; - i2c_write(0x8a, 0x1c, 1, data); - */ } return 0; } static void proc_cmd(char *input) { - if(strcmp(input, "rgb") == 0) { - printf("OK sending RGB switch command\n"); + char *endp; + + if(strcmp(input, "dbg") == 0) { + printf("OK dbg\n"); + dbgmode = 1; - /* i2c addr 8b: jungle - * subaddr 2b: Control 1 reg - * bit 1: YUV + } else if(strcmp(input, "nodbg") == 0) { + printf("OK nodbg\n"); + dbgmode = 0; + + } else if(strcmp(input, "start") == 0) { + printf("OK snooping\n"); + i2c_snoop(1); + + } else if(strcmp(input, "stop") == 0) { + i2c_snoop(0); + printf("OK stopped snooping\n"); + + } else if(strcmp(input, "av") == 0) { + printf("OK AV\n"); + + /* AV switch (22): 0 0 SVO CMB1 CMB0 INA INB 0 */ + data[0] = 0x22; + i2c_write(0x8a, 0x22, data, 1); + i2c_async(i2c_hold); + + } else if(strcmp(input, "rgb") == 0) { + printf("OK RGB\n"); + + /* Control 0 (2a): 0 IE2 RBL AKB CL3 CL2 CL1 CL0 + * Control 1 (2b): 0 0 0 0 0 0 YUV HBL */ - data[0] = 2; - i2c_write(0x8a, 0x2b, 1, data); + data[0] = 0x70; + data[1] = 0; + i2c_write(0x8a, 0x2a, data, 1); + while(i2c_mode); + i2c_write(0x8a, 0x2b, data + 1, 1); } else if(memcmp(input, "vol ", 4) == 0) { int vol = atoi(input + 4); if(vol < 1 || vol > 63) { - printf("ERR invalid vol (%s)\n", input + 4); + printf("ERR vol (%s)\n", input + 4); } else { data[0] = vol; - i2c_write(0x8a, 0x1f, 1, data); + i2c_write(0x8a, 0x1f, data, 1); printf("OK volume: %d\n", vol); } - } else if(strcmp(input, "zoom") == 0) { - data[0] = 0x3f; - i2c_write(0x8a, 0x10, 1, data); - printf("OK zoom in\n"); - - } else if(strcmp(input, "unzoom") == 0) { - data[0] = 0x20; - i2c_write(0x8a, 0x10, 1, data); - printf("OK unzoom\n"); - } else if(memcmp(input, "sat", 3) == 0) { - printf("OK saturate\n"); + printf("OK sat\n"); data[0] = atoi(input + 3); if(data[0] <= 0 || data[0] > 0x3f) { data[0] = 0x1f; } - i2c_write(0x8a, 0x1c, 1, data); - while(i2c_mode); - TWCR = 0; + i2c_write(0x8a, 0x1c, data, 1); + i2c_async(i2c_hold); /* hold I2C when done */ + + } else if(strcmp(input, "rel") == 0) { + printf("OK release\n"); + i2c_release(); + + } else if(strcmp(input, "status") == 0) { + i2c_read(0x8a, 0, data, 3); + i2c_async(printstat); + + } else if(memcmp(input, "rd ", 3) == 0) { + dump_addr = strtol(input + 3, &endp, 16); + + if(endp > input + 3 && dump_addr >= 0 && dump_addr < 2048) { + dump_count = 1; + i2c_read(0xa0 | ((dump_addr >> 7) & 0xe), dump_addr & 0xff, data, 1); + i2c_async(printdump); + } else { + printf("ERR address: %s\n", input + 3); + } + + } else if(memcmp(input, "dump ", 5) == 0) { + dump_addr = strtol(input + 5, &endp, 16); + + if(endp > input + 5 && dump_addr >= 0 && dump_addr < 2048) { + dump_count = 1; + dump_addr &= 0xff0; + i2c_read(0xa0 | ((dump_addr >> 7) & 0xe), dump_addr & 0xff, data, 16); + i2c_async(printdump); + } else { + printf("ERR address: %s\n", input + 5); + } } else if(strcmp(input, "abort") == 0) { if(i2c_mode != I2C_IDLE) { i2c_mode = I2C_IDLE; I2C_STOP(); - printf("OK aborting i2c op\n"); + printf("OK\n"); } else { printf("ERR i2c is idle\n"); } } else { - printf("ERR invalid command (%s)\n", input); + printf("ERR command (%s)\n", input); } } -void i2c_write(unsigned char addr, unsigned char subaddr, int ndata, unsigned char *data) +static void printstat(void) +{ + printf("OK status: %02x %02x %02x\n", (unsigned int)data[0], + (unsigned int)data[1], (unsigned int)data[2]); +} + +static void printdump(void) +{ + int i; + while(dump_count > 0) { + printf("OK %03x:", dump_addr); + for(i=0; i<16; i++) { + if(i == 8) putchar(' '); + + if(--dump_count >= 0) { + printf(" %02x", data[i]); + } else { + printf(" "); + } + dump_addr += 16; + } + putchar('\n'); + } +} + +void i2c_write(unsigned char addr, unsigned char subaddr, unsigned char *data, int ndata) { i2c_addr = addr & 0xfe; i2c_subaddr = subaddr; - i2c_mode = I2C_MASTER_SEND; + i2c_mode = I2C_MASTER_WRITE; i2c_ndata = ndata; i2c_data = data; i2c_seq = 0; I2C_START(); } +void i2c_read(unsigned char addr, unsigned char subaddr, unsigned char *buf, int size) +{ + i2c_addr = addr & 0xfe; + i2c_subaddr = subaddr; + i2c_mode = I2C_MASTER_READ; + i2c_ndata = size; + i2c_data = buf; + i2c_seq = 0; + I2C_START(); +} + void i2c_handle_send(void) { unsigned char state = TWSR & 0xf8; @@ -187,27 +360,25 @@ void i2c_handle_send(void) LOGNUM(state); switch(state) { - case 0x10: + case ST_REP_START: /* 0x10 */ /* repeated start, same as start, but also increment i2c_seq */ i2c_seq++; - case 0x8: + case ST_START: /* 0x8 */ /* start initiated, write the slave address */ //printf("DBG i2c SLA: %x\n", (unsigned int)i2c_addr); TWDR = i2c_addr; I2C_WRITE(); break; - case 0x18: + case ST_SLA_W_ACK: /* 0x18 */ /* slave addr sent and ACKed, send subaddr or data */ if(i2c_seq == 0) { /* this is the first packet, send subaddr */ i2c_seq++; - LOGNUM(0x100 | i2c_subaddr); TWDR = i2c_subaddr; I2C_WRITE(); } else { if(i2c_ndata--) { - LOGNUM(0x200 | *i2c_data); TWDR = *i2c_data++; I2C_WRITE(); } else { @@ -218,14 +389,14 @@ void i2c_handle_send(void) } break; - case 0x20: + case ST_SLA_W_NACK: /* 0x20 */ /* slave addr sent but not ACKed, abort */ i2c_mode = I2C_IDLE; I2C_STOP(); printf("i2c: NACK after SLA+W\n"); break; - case 0x28: + case ST_WR_ACK: /* 0x28 */ /* data (or subaddr) sent and ACKed, send more data (or restart) if available */ #if 0 if(i2c_seq == 0) { @@ -235,7 +406,6 @@ void i2c_handle_send(void) #endif /* data was sent, send more data or stop */ if(i2c_ndata--) { - LOGNUM(0x200 | *i2c_data); TWDR = *i2c_data++; I2C_WRITE(); } else { @@ -245,7 +415,7 @@ void i2c_handle_send(void) // } break; - case 0x30: + case ST_WR_NACK: /* 0x30 */ /* data (or subaddr) sent but not ACKed */ if(i2c_seq == 0) { /* NACK after subaddr, abort */ @@ -253,20 +423,20 @@ void i2c_handle_send(void) } else { /* NACK after data */ if(i2c_ndata) { - printf("i2c: NACK with %d data packets pending\n", i2c_ndata); + printf("i2c: NACK with %d pending\n", i2c_ndata); } } i2c_mode = I2C_IDLE; I2C_STOP(); break; - case 0x38: + case ST_ARBLOST: /* 0x38 */ /* arbitration lost */ - printf("i2c: arbitration lost\n"); + printf("i2c: arb lost\n"); I2C_START(); break; - case 0: + case ST_INVALID: /* 0 */ printf("i2c: invalid start/stop\n"); i2c_mode = I2C_IDLE; I2C_STOP(); @@ -281,46 +451,252 @@ void i2c_handle_send(void) void i2c_handle_recv(void) { - printf("i2c: recv unimplemented\n"); + unsigned char state = TWSR & 0xf8; + + LOGNUM(state); + + switch(state) { + case ST_START: /* 0x8 */ + TWDR = i2c_addr; /* start a *write* (we need to send the subaddress before reading) */ + I2C_WRITE(); + break; + + case ST_REP_START: /* 0x10 */ + /* repeated start, now we can issue the actual read */ + TWDR = i2c_addr | 1; + I2C_WRITE(); + break; + + case ST_SLA_W_ACK: /* 0x18 */ + /* SLA+W means we just started the write part, need to send the subaddress */ + TWDR = i2c_subaddr; + I2C_WRITE(); + break; + + case ST_SLA_W_NACK: + /* slave addr sent but not ACKed, abort */ + i2c_mode = I2C_IDLE; + I2C_STOP(); + printf("i2c: NACK after SLA+W (R)\n"); + break; + + case ST_WR_ACK: /* 0x28 */ + /* the subaddress write was ACKed, rep-start to issue the read */ + I2C_START(); + break; + + case ST_WR_NACK: + /* the subaddress write was not ACKed, abort */ + i2c_mode = I2C_IDLE; + I2C_STOP(); + printf("i2c: NACK after subaddr (R)\n"); + break; + + case ST_SLA_R_ACK: /* 0x40 */ + /* SLA+R was ACKed send ACK to start receiving */ + I2C_READ_ACK(); + break; + + case ST_RD_ACK: /* 0x50 */ + /* ... or last read was ACKed, again read next and ACK for more */ + *i2c_data++ = TWDR; + if(--i2c_ndata > 0) { + I2C_READ_ACK(); + } else { + I2C_READ_NACK(); + } + break; + + case ST_SLA_R_NACK: /* 0x48 */ + /* SLA+R was sent but ACK was not received, abort */ + i2c_mode = I2C_IDLE; + I2C_STOP(); + printf("i2c: NACK after SLA+R\n"); + break; + + case ST_RD_NACK: /* 0x58 */ + /* read without ACK, we get this after we send a NACK, or this is the last byte (?) */ + if(i2c_ndata > 0) { + *i2c_data++ = TWDR; + i2c_ndata--; + } + i2c_mode = I2C_IDLE; + I2C_STOP(); + break; + + case ST_ARBLOST: /* 0x38 */ + /* arbitration lost */ + printf("i2c: arb lost\n"); + I2C_START(); + break; + + case ST_INVALID: /* 0 */ + printf("i2c: invalid start/stop\n"); + i2c_mode = I2C_IDLE; + I2C_STOP(); + break; + + default: + printf("i2c: unexpected state (R): 0x%x\n", state); + i2c_mode = I2C_IDLE; + I2C_STOP(); + } } ISR(TWI_vect) { - uint16_t ev; - switch(i2c_mode) { - case I2C_MASTER_SEND: + case I2C_MASTER_WRITE: i2c_handle_send(); break; - case I2C_MASTER_RECV: + case I2C_MASTER_READ: i2c_handle_recv(); break; + } +} - default: - /* log traffic on the bus */ - ev = (uint16_t)(TWSR & 0xf8) << 8; - switch(TWSR & 0xf8) { - case 0x80: /* own SLA+W received, ack */ - case 0x88: /* own SLA+W recv, no-ack */ - case 0x90: /* general recv, ack */ - case 0x98: /* general recv, no-ack */ - ev |= TWDR; - break; - - default: - break; - } +void i2c_wait(void) +{ + while(i2c_mode); +} + +void i2c_async(void (*donecb)(void)) +{ + async_func = donecb; +} + +void i2c_check_async(void) +{ + if(async_func && i2c_mode == I2C_IDLE) { + async_func(); + async_func = 0; + } +} + +void i2c_hold(void) +{ + TWCR = 0; /* make sure the AVR i2c hardware is disabled */ + DDRC = 0x20; /* ... and drive SCL pin low */ + PORTC = 0; +} + +void i2c_release(void) +{ + DDRC = 0; +} + +#define PC_SCL 5 +#define PC_SDA 4 +#define PC_SCL_BIT (1 << PC_SCL) +#define PC_SDA_BIT (1 << PC_SDA) - /* append data to the event queue */ - TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT; +static unsigned char pcprev, state; +static unsigned char value; +static int nbits; - evq[evq_wr] = ev; - evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); - if(evq_wr == evq_rd) { +/* use PCINT12,13 to snoop i2c traffic */ +void i2c_snoop(int onoff) +{ + snooping = onoff; + + if(!onoff) { +#ifdef USE_PCINT + /* to stop snooping just disable the interrupt */ + cli(); + PCMSK1 = 0; + PCICR &= ~(1 << PCIE1); + sei(); +#endif + return; + } + + TWCR = 0; /* make sure the i2c hw is disabled */ + DDRC = 0; + PORTC = 0; + +#ifdef USE_PCINT + cli(); + PCICR |= 1 << PCIE1; /* enable the pin-change interrupt */ + PCMSK1 = (1 << PCINT12) | (1 << PCINT13); +#endif + + pcprev = PINC; + state = 0; + value = 0; + nbits = 0; +#ifdef USE_PCINT + sei(); +#endif +} + +#ifdef USE_PCINT +ISR(PCINT1_vect) +#else +void snoop(void) +#endif +{ + unsigned char pinc, delta; + + pinc = PINC; + delta = pinc ^ pcprev; + pcprev = pinc; + + if(delta & PC_SDA_BIT) { + if(pinc & PC_SCL_BIT) { + /* clock is high, so this is either a start or a stop */ + if(pinc & PC_SDA_BIT) { + EVLOG(EV_STOP, 0); + state = 0; + } else { + EVLOG(EV_START, 0); + state = EV_START; + value = 0; + nbits = 0; + } + return; + } + } + + if(!state) { + uint16_t val, ev; + if(evq_wr != evq_rd) { + val = evq[evq_rd]; evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); + ev = val >> 8; + if(ev <= EV_STOP) { + printf("%s\n", evname[ev]); + } else { + printf("%s: %02x\n", evname[ev], val & 0xff); + } } + return; + } - PORTB = 1; + if(delta & PC_SCL_BIT) { + if(pinc & PC_SCL_BIT) { + /* clock is going high, shift SDA */ + value = (value << 1) | ((pinc >> PC_SDA) & 1); + if(++nbits >= 8) { + switch(state) { + case EV_START: + state = (value & 1) ? EV_SLA_R : EV_SLA_W; + EVLOG(state, value & 0xfe); + break; + case EV_SLA_W: + case EV_SLA_R: + state = EV_DATA; + EVLOG(state, value); + case EV_DATA: + //EVLOG(state, value); + default: + break; + } + nbits = 0; + value = 0; + } + } } + + PORTB ^= 1; }