rom_i2c_readReg dissection

On this article I will disect the rom_i2c_readReg function. This function exhibits a really insteresting behaviour, suposedly it accesses the internal i2c which connects the peripherals not directly mapped to memory (PLLs, and whatnot).

Lets get started:

 a couple of i2c related functions that live in the rom:
uint8_t rom_i2c_readReg(uint8_t block, uint8_t host_id, uint8_t reg_add);
void rom_i2c_writeReg(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t data);

Before speculating over how the registers work, let's take a look into the disassembly

 rom_i2c_readReg(103, 4, 1);
; uint8_t rom_i2c_readReg(uint8_t block => a2, uint8_t host_id => a3, uint8_t reg_add => a4) => a2;
40007268:  f33b81    l32r   a8, 0x40003f54    ; a8 = *(uint32_t*)0x40003f54 => 0x60000A00
4000726b:  116480    slli   a6, a4, 8         ; a6 = reg_add << 8           
4000726e:  1173e0    slli   a7, a3, 2         ; a7 = host_id << 2           
40007271:  206260    or a6, a2, a6            ; a6 = block | a6             
40007274:  278a      add.n  a2, a7, a8        ; a2 = a7 + a8                
40007276:  0020c0    memw                     ; memory wait (you can ignore this, see the ISA)
40007279:  c06262    s32i   a6, a2, 0x300     ; *(uint32_t*)(a2 + 0x300) = a6
4000727c:  0020c0    memw                     ;
4000727f:  c02252    l32i   a5, a2, 0x300     ; a5 = *(uint32_t*)(a2 + 0x300) => a5 
40007282:  0a7597    bbci   a5, 25, bit_clear ; if(!(a5 & (1 << 25))) goto bit_clear;
loop:                                         ;
40007285:  0020c0    memw                     ; basically this block waits until bit25 is clear
40007288:  c02272    l32i   a7, a2, 0x300     ; a7 = *(uint32_t*)(a2 + 0x300) => register is re-read
4000728b:  f03d      nop.n                    ; no operation
4000728d:  f4f797    bbsi   a7, 25, bit_set   ; if(a5 & (1 << 25)) goto loop;
bit_clear:                                    ;
40007290:  0020c0    memw                     ;
40007293:  c02222    l32i   a2, a2, 0x300     ; a2 = *(uint32_t*)(a2 + 0x300)
40007296:  752020    extui  a2, a2, 16, 8     ; a2 = (a2 >> 16) & 0xff
40007299:  f00d      ret.n                    ; returns a2 

This basically writes, and reads the registers starting at 0x60000d00 adding some transformations to its arguments. I've observed that these registers are a two part 16bit register. The least significant 16 bits correspond to some kind of address, and writing to this part causes the data on the most significant 16 bits to change.

As seen on the code above, it's clear that there is a flag that is clear when the read is complete (bit 25 or 0x200 on the most significant 16 bits), and also note that reading some registers hangs the ESP8266.

In summary; the internal i2c registers are probably defined as:

struct {
    uint8_t flags;
    uint8_t data;
    uint16_t current_address;
};