MITM en el bus I2C

8

He estado intentando diseñar un módulo que me permita modificar las respuestas de esclavo seleccionadas en un bus I2C. Aquí está la configuración del bus original (los pull-ups y las conexiones de alimentación no se muestran para mayor claridad:

Solohay2dispositivosenestebusysolo100kHz.UncontroladorMCU(maestroI2C)yellectordetarjetasRFID(esclavoI2C)NXPPN512.NopuedomodificarelfirmwaredelcontroladorocambiarlastransaccionesdelbusI2C.LobuenoesqueelControladorsoloenvía2tiposdetransacciones:

Master(WriteRegister)-<s><address+W><registernumber><data><p>Master(ReadRegister)-<s><address+W><registernumber><p><s><address+R><data><p>

Loquequierohaceresreemplazarlosbytesdedatosseleccionadosdurantelalecturadelregistromaestroconmispropiosbytes.PuedoenviarlosnúmerosderegistroqueelMCUquiereleeramiPCatravésdeUART(921.6kbaud).PuedoprocesarlosenC/C++oPythonallí.Cuandoreciboelnúmeroderegistrocuyovalornecesitaserreemplazado,puedoenviarunbytefalsodevueltaamidispositivoyseencargarádedevolverloalcontroladorquereemplazalarespuestadelatarjetaoriginal.

Alprincipio,dividíelbusI2Cendosbuses:

ProbéArduinoNanoyluegounCPLDusandoelestiramientodelreloj.ElhardwareI2CdeATmega328orientadohaciaelcontroladorMCUnopudomantenersealdía,yaqueaveceslasecuenciadeiniciosegeneróantesde5usdespuésdelciclodedetenciónanterior.AsíquedevezencuandoelAVRNAKeraunatransaccióndelectura.ElCPLDpodíamanejarlavelocidaddeparada/inicioyresultóqueelestiramientodelbusestabadeshabilitadoenlaMCU.

Semeocurrióunaideadequepuedo"predecir" la lectura del Registro maestro detectando una escritura de un solo byte, ya que estoy seguro de que es seguida por una lectura. Parece que tuve suficiente tiempo durante el siguiente ciclo de lectura de escritura de dirección para ingresar el byte del esclavo. Eso no funcionó del todo. Las transacciones del bus parecían estar bien al principio (aproximadamente los primeros 5 segundos) pero luego el controlador suspendió todas las comunicaciones en el bus como si detectara que no está hablando directamente con la etiqueta leída.

El lector de tarjetas también puede generar interrupciones en el maestro. Las IRQ son un temporizador o un evento basado. Atribuí el problema a la demora que estaba introduciendo intrínsecamente en el autobús. Puede que me haya equivocado, pero se me ocurrió otro diseño de "retraso cero".

LaideaesquesolopuedoromperlalíneaSDAydejarlalíneaSCLconectadaentreelmaestroyelesclavo.Deestamaneratodavíapuedoreemplazarbytesenlalíneadedatosencualquierdirección.EldiseñodemostrósermáscomplicadoyaquetengoquecontrolarladireccióndelalíneaSDAenfuncióndelciclodelbus.AquíestáelcódigoVHDLquemanejalastransaccionesdelbusyenvíabyteshexadecimalesatravésdeUARTalacomputadora.Larecepcióndebytesdelacomputadoraaúnnoestáimplementada:

libraryieee;useieee.std_logic_1164.all;useieee.numeric_std.all;entityI2C_Snifferisport(clk:instd_logic;scl_master:instd_logic;sda_master:inoutstd_logic;sda_slave:inoutstd_logic;tx:outstd_logic);endentityI2C_Sniffer;architecturearchofI2C_SnifferissignalclkDiv:std_logic_vector(7downto0):=(others=>'0');typeI2C_STATEis(I2C_IDLE,I2C_MASTER_WRITE,I2C_SLAVE_ACK,I2C_MASTER_READ,I2C_MASTER_ACK);signali2cState:I2C_STATE:=I2C_IDLE;typeI2C_BUS_DIRis(MASTER_TO_SLAVE,SLAVE_TO_MASTER);signali2cBusDir:I2C_BUS_DIR:=MASTER_TO_SLAVE;signali2cRxData:std_logic_vector(7downto0);signali2cCntr:integerrange0to8:=0;signali2cAddr:std_logic:='1';signali2cCmd:std_logic:='0';signalscl_d:std_logic:='1';signalscl:std_logic:='1';signalsda_d:std_logic:='1';signalsda:std_logic:='1';--StrobesforSCLedgesandStart/Stopbitssignalstart_strobe:std_logic:='0';signalstop_strobe:std_logic:='0';signalscl_rising_strobe:std_logic:='0';signalscl_falling_strobe:std_logic:='0';typeUART_STATEis(UART_IDLE,UART_START,UART_DATA,UART_STOP);signaluartState:UART_STATE:=UART_IDLE;signaluartTxRdy:std_logic:='0';signaluartTxData:std_logic_vector(7downto0);signaluartCntr:integerrange0to8:=0;beginCLK_DIV:process(clk)beginifrising_edge(clk)thenclkDiv<=std_logic_vector(unsigned(clkDiv)+1);endif;endprocess;I2C_STROBES:process(clk)beginifrising_edge(clk)then--PipelinedSDAandSCLsignalsscl_d<=scl_master;scl<=scl_d;scl_rising_strobe<='0';ifscl='0'andscl_d='1'thenscl_rising_strobe<='1';endif;scl_falling_strobe<='0';ifscl='1'andscl_d='0'thenscl_falling_strobe<='1';endif;ifi2cBusDir=MASTER_TO_SLAVEthensda_d<=sda_master;sda<=sda_d;elsesda_d<=sda_slave;sda<=sda_d;endif;start_strobe<='0';ifsda_d='0'andsda='1'andscl='1'andscl_d='1'thenstart_strobe<='1';endif;stop_strobe<='0';ifsda_d='1'andsda='0'andscl='1'andscl_d='1'thenstop_strobe<='1';endif;endif;endprocess;BUS_DIR:process(sda_master,sda_slave,i2cBusDir)beginifi2cBusDir=MASTER_TO_SLAVEthensda_slave<=sda_master;sda_master<='Z';elsesda_master<=sda_slave;sda_slave<='Z';endif;endprocess;I2C:process(clk)beginifrising_edge(clk)thenuartTxRdy<='0';casei2cStateiswhenI2C_IDLE=>i2cBusDir<=MASTER_TO_SLAVE;ifstart_strobe='1'theni2cAddr<='1';i2cCntr<=0;i2cState<=I2C_MASTER_WRITE;endif;--MasterWrite(Address/Data)whenI2C_MASTER_WRITE=>i2cBusDir<=MASTER_TO_SLAVE;ifstop_strobe='1'theni2cState<=I2C_IDLE;uartTxData<="00001010";
                        uartTxRdy <= '1';
                end if;

                if scl_rising_strobe = '1' then
                    if i2cCntr <= 7 then
                        i2cRxData(7 - i2cCntr) <= sda;
                        i2cCntr <= i2cCntr + 1;
                    end if;
                end if;

                if i2cCntr = 4 then
                    case i2cRxData(7 downto 4) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    case i2cRxData(3 downto 0) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    if scl_falling_strobe = '1' then
                        i2cState <= I2C_SLAVE_ACK;

                        if i2cAddr = '1' then
                            i2cCmd <= i2cRxData(0);
                            i2cAddr <= '0';
                        end if;
                    end if;
                end if;

            when I2C_SLAVE_ACK =>
                i2cBusDir <= SLAVE_TO_MASTER;

                if scl_falling_strobe = '1' then
                    i2cCntr <= 0;

                    if i2cCmd = '0' then
                        i2cState <= I2C_MASTER_WRITE;
                    else
                        i2cState <= I2C_MASTER_READ;
                    end if;
                end if;

            when I2C_MASTER_READ =>
                i2cBusDir <= SLAVE_TO_MASTER;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                        uartTxData <= "00001010";
                        uartTxRdy <= '1';
                end if;

                if scl_rising_strobe = '1' then
                    if i2cCntr <= 7 then
                        i2cRxData(7 - i2cCntr) <= sda;
                        i2cCntr <= i2cCntr + 1;
                    end if;
                end if;

                if i2cCntr = 4 then
                    case i2cRxData(7 downto 4) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    case i2cRxData(3 downto 0) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 and scl_falling_strobe = '1' then
                    i2cState <= I2C_MASTER_ACK;
                end if;

            when I2C_MASTER_ACK =>
                i2cBusDir <= MASTER_TO_SLAVE;
                if scl_falling_strobe = '1' then
                    i2cCntr <= 0;
                end if;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                    uartTxData <= "00001010"; -- \n
                    uartTxRdy <= '1';
                end if;
        end case;
    end if;
end process;


UART: process (clk, clkDiv(1), uartTxRdy)
begin
    if rising_edge(clk) then
        case uartState is
            when UART_IDLE =>
                if uartTxRdy = '1' then
                    uartState <= UART_START;
                end if;

            when UART_START =>
                if clkDiv(1 downto 0) = "00" then
                    tx <= '0';
                    uartState <= UART_DATA;
                    uartCntr <= 0;
                end if;

            when UART_DATA =>
                if clkDiv(1 downto 0) = "00" then
                    if uartCntr <= 7 then
                        uartCntr <= uartCntr + 1;
                        tx <= uartTxData(uartCntr);
                    else
                        tx <= '1';
                        uartState <= UART_STOP;
                    end if;
                end if;

            when UART_STOP =>
                if clkDiv(1 downto 0) = "00" then
                    tx <= '1';
                    uartState <= UART_IDLE;
                end if;
        end case;
    end if;
  end process;
end architecture arch;

A continuación se muestran las transacciones de bus capturadas con el CPLD que controla la línea SDA.

Registro de escritura:

Registroleído:

Puedes ver algunos fallos cuando cambia la dirección del bus. Esto se debe a las diferencias en el tiempo entre el CPLD que cambia la dirección del bus y el lector de tarjetas que genera un ACK. El nivel de ACK parece ser estable en el flanco ascendente de la SCL. Por lo que sé, eso es todo lo que necesitas.

Con esta cosa en su lugar, el controlador se comporta de la misma manera que con los buses divididos suspendiendo cualquier actividad del bus en unos pocos segundos. También pruebo que w Arduino se burla de MCU y genera tráfico de bus para mí y parece que Arduino también se congela de vez en cuando. Así que supongo que puedo tener algún tipo de problema con la máquina de estados VHDL donde, en algunas condiciones, me quedo atascado en un estado sin salida. ¿Alguna idea?

    
pregunta Alexxx

2 respuestas

6

Creo que intentar hacer trucos como los que has estado haciendo ahora es buscar problemas, exactamente con el tipo de síntomas que estás experimentando. Básicamente estás intentando hacer trampa y espero que no te atrapen.

Lo único que no has probado, según tu descripción, es una completa emulación de este lector de tarjetas. Realmente no ha explicado qué hace exactamente y qué tan complicado es, pero a juzgar por lo que el maestro está enviando no es tan complicado.

Use un microcontrolador con capacidad de hardware IIC slave. Eso está conectado al maestro. El firmware emula el lector de tarjetas. Dado que lo único que el maestro lee es una secuencia de registros, la otra parte del firmware se comunica de forma completamente asíncrona con el lector de tarjetas para obtener información y controlarla. Esto también significa que las líneas de reinicio e IRQ también están separadas.

Si se hace bien, esto tiene que funcionar, ya que no hay trampas. El lector de tarjetas ve un controlador que envía comandos y realiza la lectura exactamente de la forma en que estaba destinado a ser utilizado. Esto incluye responder a los eventos de IRQ.

El maestro cree que está hablando directamente con un lector de tarjetas real porque emula todas sus operaciones como si fueran reales, incluido el comportamiento de reinicio y IRQ.

Esto puede parecer más trabajo que un atasco rápido y sucio de un byte diferente en el truco del bus, pero como lo encontró, no es tan rápido y puede que siempre tenga algunos problemas de tiempo. Con una emulación completa, se eliminan todas las restricciones de tiempo. Si su emulación aún no ha alcanzado algo que el lector de tarjetas ha hecho, entonces actúa como si el maestro no hubiera sucedido todavía. Básicamente, finges que no ha pasado nada nuevo hasta que tu emulación esté lista para responder al evento en todos los aspectos.

Esto significa que realmente tiene dos partes asíncronas del firmware: la emulación IIC del lector presentada al maestro y un controlador de lector de tarjetas completo que le permite mantener todo su estado en vivo en su memoria interna.

Ya que no estás haciendo trampa, esto tiene que funcionar si se hace bien. El único problema a nivel del sistema es que habrá un retraso en la visualización del maestro y en la causa de las acciones del lector de tarjetas que el sistema existente. Esto no suena como un gran problema para un "lector de tarjetas", y teniendo en cuenta que este retraso probablemente sea de 10 milisegundos en el peor de los casos. Ciertamente, no debería ser perceptible en una escala de tiempo humana.

Tenga en cuenta que la comunicación entre su emulador y el lector de tarjetas no se limita a los 100 kbits / s utilizados actualmente. Debe ejecutarlo tan rápido como lo permita el lector de tarjetas y su hardware. Después de todo, en ese enlace serás el maestro, así que tienes el reloj. De nuevo, con una arquitectura de firmware adecuada y tareas asíncronas, esto no debería importar. De hecho, su controlador probablemente se comunicará más a menudo y obtendrá más datos del lector de tarjetas que el maestro de su emulador.

    
respondido por el Olin Lathrop
0

Sugeriría que estuvieras en el camino correcto con un Arduino Nano como MITM, aunque creo que sería mejor con dos.

El NXP-PN512 funcionará a una velocidad de reloj de 3.4 Mhz, por lo que le sugiero que use algo del orden de 1.5 - 2 MHz para la MCU de la derecha que habla con el Reader.
Como la MCU de la izquierda está configurada a 100 kHz, una vez que haya reconocido cualquier octeto de transacción (dirección / registro-WR), puede copiarlo en un bus paralelo de 8 bits (o incluso más ancho) entre las MCU y enviar los comandos al lector en menos de un reloj en el canal I2C de baja velocidad. Igualmente, la recepción de un byte del lector se logra en menos de una hora de reloj en el bus lento, lo que proporciona el tiempo adecuado para configurar el byte de respuesta.

Supongo que aquí es posible que deba traducir varios bytes como una ID de NFC y no solo una conversión de byte a byte (lo que requiere menos tiempo).

El principal problema que vería entonces es que si necesita serializar varios bytes hacia / desde la PC para asignar sus cambios, la sincronización se vuelve aún más crítica. Si hubiera una manera de construir su algoritmo / tabla de cambio de mapeo en la MCU de la izquierda, parecería un mejor enfoque, aunque la resolución de un mapeo de identificador de múltiples bye sigue siendo el mayor desafío.

Si me equivoco y solo necesita asignar un solo byte de identificación de tarjeta, entonces esto podría funcionar.

En sus primeras pruebas con el Arduino, ¿se aseguró de que todas las interrupciones estuvieran desactivadas (al menos solo TWI en uso)? Si no lo hiciste, esto puede haber alterado tu tiempo.

    
respondido por el Jack Creasey

Lea otras preguntas en las etiquetas