diff --git a/iso7816/CMakeLists.txt b/iso7816/CMakeLists.txt index a952ac4..f050233 100644 --- a/iso7816/CMakeLists.txt +++ b/iso7816/CMakeLists.txt @@ -1,6 +1,7 @@ add_executable(iso7816_cardem) -# pico_generate_pio_header(pio_uart_rx ${CMAKE_CURRENT_LIST_DIR}/uart_rx.pio) +pico_generate_pio_header(iso7816_cardem ${CMAKE_CURRENT_LIST_DIR}/iso7816_rx.pio) +pico_generate_pio_header(iso7816_cardem ${CMAKE_CURRENT_LIST_DIR}/iso7816_tx.pio) target_sources(iso7816_cardem PRIVATE iso7816_cardem.c) @@ -9,5 +10,6 @@ target_link_libraries(iso7816_cardem PRIVATE pico_multicore hardware_pio ) +pico_enable_stdio_usb(iso7816_cardem 1) pico_add_extra_outputs(iso7816_cardem) diff --git a/iso7816/iso7816_cardem.c b/iso7816/iso7816_cardem.c index e69de29..7defa78 100644 --- a/iso7816/iso7816_cardem.c +++ b/iso7816/iso7816_cardem.c @@ -0,0 +1,213 @@ +#include + +#include "pico/stdlib.h" +#include "pico/multicore.h" +#include "hardware/pio.h" +#include "hardware/uart.h" +#include "hardware/clocks.h" + +#include "tusb.h" + +#include "iso7816_tx.pio.h" + +#define GPIO_SIM_VCC 18 /* pico pin 24 */ +#define GPIO_SIM_RST 19 /* pico pin 25 */ +#define GPIO_SIM_IO 21 /* pico pin 27 */ +#define GPIO_SIM_CLK 20 /* must be at GP20 due to the GPIN0 function; pico pin 26 */ + +enum cardem_state { + CST_WAIT_VCC, + CST_VCC_WAIT_CLK, + CST_VCC_CLK_WAIT_RST, + CST_DELAY_BEFORE_ATR, + CST_IN_ATR, + CST_WAIT_TPDU_HDR, + CST_IN_TPDU_HDR, +}; + +struct cardem_uart_instance { + enum cardem_state state; + uint32_t vcc_freq_khz; + volatile bool vcc_present; + volatile bool rst_active; + + PIO pio; + uint sm; + uint tx_prog_offset; + +}; + +static struct cardem_uart_instance g_cst[1]; + +const uint8_t g_atr[] = { 0x3B, 0x80, 0x80, 0x81, 0x1F, 0xC7, 0x59 }; + +static void gpio_callback(uint gpio, uint32_t events) +{ + switch (gpio) { + case GPIO_SIM_VCC: + if (events & GPIO_IRQ_EDGE_RISE) + g_cst[0].vcc_present = true; +#if 1 + if (events & GPIO_IRQ_EDGE_FALL) + g_cst[0].vcc_present = false; +#endif + //printf("VCC=%d\n", g_cst[0].vcc_present); + break; + case GPIO_SIM_RST: + if (events & GPIO_IRQ_EDGE_RISE) + g_cst[0].rst_active = false; +#if 1 + if (events & GPIO_IRQ_EDGE_FALL) + g_cst[0].rst_active = true; +#endif + //printf("RST=%d\n", g_cst[0].rst_active); + break; + default: + printf("unhandled GP%u: 0x%x\n", gpio, events); + break; + } +} + +static void cardem_main_state_chg(struct cardem_uart_instance *cst, enum cardem_state new_st) +{ + //printf("State %u -> %u\n", cst->state, new_st); + cst->state = new_st; +} + +static void cardem_main_loop(struct cardem_uart_instance *cst) +{ + /* global reset on power-off */ + if (cst->state != CST_WAIT_VCC && !cst->vcc_present ) { + cardem_main_state_chg(cst, CST_WAIT_VCC); + return; + } + + switch (cst->state) { + case CST_WAIT_VCC: + if (cst->vcc_present) + cardem_main_state_chg(cst, CST_VCC_WAIT_CLK); + break; + case CST_VCC_WAIT_CLK: + /* determine CLK frequency */ + cst->vcc_freq_khz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLKSRC_GPIN0); + if (cst->vcc_freq_khz) { + //printf("Determined CLK freq: %u kHz\n", cst->vcc_freq_khz); + cardem_main_state_chg(cst, CST_VCC_CLK_WAIT_RST); + } + break; + case CST_VCC_CLK_WAIT_RST: +#if 1 + if (!cst->rst_active) + cardem_main_state_chg(cst, CST_DELAY_BEFORE_ATR); + break; +#endif + case CST_DELAY_BEFORE_ATR: + /* FIXME: actually delay */ + if (true) { + cardem_main_state_chg(cst, CST_IN_ATR); + /* configure the clock divider for the "correct" baud rate */ + iso7816_tx_program_init(cst->pio, cst->sm, cst->tx_prog_offset, GPIO_SIM_IO, + (cst->vcc_freq_khz * 1000) / 372); + /* FIXME: actually start transmission via PIO */ + iso7816_tx_program_puts(cst->pio, cst->sm, g_atr, sizeof(g_atr)); + printf("ATR sent\n"); + cardem_main_state_chg(cst, CST_WAIT_TPDU_HDR); + } + break; + case CST_IN_ATR: + break; + case CST_WAIT_TPDU_HDR: + break; + case CST_IN_TPDU_HDR: + break; + } +} + +static void cardem_inst_init(struct cardem_uart_instance *cst) +{ + cst->state = CST_WAIT_VCC; + cst->vcc_freq_khz = 0; + cst->vcc_present = false; + cst->rst_active = false; + cst->pio = pio0; + cst->sm = 0; + cst->tx_prog_offset = pio_add_program(cst->pio, &iso7816_tx_program); +} + +static void measure_freqs(void) { + uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY); + uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY); + uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC); + uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS); + uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI); + uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB); + uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC); + uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC); + + printf("pll_sys = %dkHz\n", f_pll_sys); + printf("pll_usb = %dkHz\n", f_pll_usb); + printf("rosc = %dkHz\n", f_rosc); + printf("clk_sys = %dkHz\n", f_clk_sys); + printf("clk_peri = %dkHz\n", f_clk_peri); + printf("clk_usb = %dkHz\n", f_clk_usb); + printf("clk_adc = %dkHz\n", f_clk_adc); + printf("clk_rtc = %dkHz\n", f_clk_rtc); + + // Can't measure clk_ref / xosc as it is the ref +} + +int main() +{ + gpio_debug_pins_init(); +#if 1 + //stdio_init_all(); + stdio_usb_init(); + + while (!tud_cdc_connected()) { + printf("."); + sleep_ms(500); + } +#else + /* 115200 bps on GP0/GP1 of RP2040 (at least on pico) */ + setup_default_uart(); +#endif + printf("Starting ISO7816 cardem playground\n"); + + measure_freqs(); + + cardem_inst_init(&g_cst[0]); + + gpio_set_irq_callback(gpio_callback); + irq_set_enabled(IO_IRQ_BANK0, true); + + /* GP20 (CLK) as external clock input GPIN0 */ + gpio_set_function(GPIO_SIM_CLK, GPIO_FUNC_GPCK); + gpio_pull_down(GPIO_SIM_CLK); + + /* GPIO_SIM_VCC as input; edge-triggered interrupts */ + gpio_init(GPIO_SIM_VCC); + gpio_set_dir(GPIO_SIM_VCC, false); + gpio_set_slew_rate(GPIO_SIM_VCC, GPIO_SLEW_RATE_SLOW); + gpio_pull_down(GPIO_SIM_VCC); + gpio_set_input_enabled(GPIO_SIM_VCC, true); + gpio_set_irq_enabled(GPIO_SIM_VCC, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); + + /* GPIO_SIM_RST as input; edge-triggered interrupts */ + gpio_init(GPIO_SIM_RST); + gpio_set_dir(GPIO_SIM_RST, false); + gpio_set_slew_rate(GPIO_SIM_RST, GPIO_SLEW_RATE_SLOW); + gpio_pull_up(GPIO_SIM_RST); + gpio_set_input_enabled(GPIO_SIM_RST, true); + gpio_set_irq_enabled(GPIO_SIM_RST, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); + + gpio_pull_up(GPIO_SIM_IO); + gpio_pull_up(GPIO_SIM_IO); + gpio_set_function(GPIO_SIM_IO, GPIO_FUNC_PIO0); + + + printf("Entering main loop\n"); + while (true) { + cardem_main_loop(&g_cst[0]); + } + +} diff --git a/iso7816/iso7816_rx.pio b/iso7816/iso7816_rx.pio new file mode 100644 index 0000000..9208d30 --- /dev/null +++ b/iso7816/iso7816_rx.pio @@ -0,0 +1,49 @@ +.program iso7816_rx + +; We assume 5-times oversampling here, i.e. PIO clock rate is running at 5x etu rate + +start: + wait 0 pin 0 ; Stall until start bit is asserted + set x, 9 [7] ; Preload bit counter, delay until eye of first data bit +bitloop: + in pins, 1 ; sample one data bit + jmp x-- bitloop [3] ; loop 10 times, each loop iteration is 5 cycles + jmp pin good_stop ; Check stop bit (should be high) + + irq 4 rel ; Either a framing error or a break. Set a sticky flag, + wait 1 pin 0 ; and wait for line to return to idle state. + jmp start ; don't push data if we didn't see good framing + +good_stop: ; No delay before returning to start; a little slack is + push ; important in case the TX clock is slightly too fast + +% c-sdk { +static inline void iso7816_rx_program_init(PIO pio uint sm, uint offset, uint pin, uint buad) +{ + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); + pio_gpio_init(pio, pin); + gpio_pull_up(pin); + + pio_sm_config c = iso7816_rx_program_get_default_config(offset); + sm_config_set_in_pins(&c, pin); + sm_config_set_jmp_pin(&c, pin); + /* shift to right; autopush disabled */ + sm_config_set_in_shift(&c, true, false, 32); + float div = (float) clock_get_hz(clk_sys) / (5 * baud); + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +static inline char iso7816_rx_program_getc(PIO pio, uint sm) +{ + /* read upper 16 bits of the FIFO, as data is left-justified */ + io_rw_16 *rxfifo_shift = (io_rw_16 *)&pio->fxf[sm] + 2; + while (pio_sm_is_rx_fifo_empty(pio, sm)) + tight_loop_cointents(); + /* FIXME: evaluate parity bit */ + return (char ) (*rxfifo_shift >> 7) +} + +%} diff --git a/iso7816/iso7816_tx.pio b/iso7816/iso7816_tx.pio new file mode 100644 index 0000000..4e7ac63 --- /dev/null +++ b/iso7816/iso7816_tx.pio @@ -0,0 +1,78 @@ +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program iso7816_tx +.side_set 1 opt + +; An ISO7816-3 UART transmit program. Adapted from pico-examples/pio/uart_tx/uart_tx.pio +; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. + +; The general idea is + + pull side 1 [4] ; Assert stop bit, or stall with line in idle state + set x, 8 side 0 [4] ; Preload bit counter, assert start bit for 5 clocks +bitloop: ; This loop will run 8 times (8n1 UART) + out pins, 1 ; Shift 1 bit from OSR to the first OUT pin + jmp x-- bitloop [3] ; Each loop iteration is 5 cycles. + + set x, 8 side 1 [4] ; one additional stop bit so we get to GT=12etu + + +% c-sdk { +#include "hardware/clocks.h" + +static inline void iso7816_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint baud) +{ + // Tell PIO to initially drive output-high on the selected pin, then map PIO + // onto that pin with the IO muxes. + pio_sm_set_pins_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx); + pio_sm_set_pindirs_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx); + pio_gpio_init(pio, pin_tx); + + pio_sm_config c = iso7816_tx_program_get_default_config(offset); + + // OUT shifts to right, no autopull + sm_config_set_out_shift(&c, true, false, 32); + + // We are mapping both OUT and side-set to the same pin, because sometimes + // we need to assert user data onto the pin (with OUT) and sometimes + // assert constant values (start/stop bit) + sm_config_set_out_pins(&c, pin_tx, 1); + sm_config_set_sideset_pins(&c, pin_tx); + + // SM transmits 1 bit per 5 execution cycles. + float div = (float)clock_get_hz(clk_sys) / (5 * baud); + printf("baud=%u, div=%f\n", baud, div); + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +static inline bool compute_even_parity_bit(uint8_t x) +{ + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + return (x) & 1; +} + +static inline void iso7816_tx_program_putc(PIO pio, uint sm, uint8_t c) +{ + uint32_t c_with_parity = c; + + if (compute_even_parity_bit(c)) + c_with_parity |= 0x100; + + pio_sm_put_blocking(pio, sm, c_with_parity); +} + +static inline void iso7816_tx_program_puts(PIO pio, uint sm, const uint8_t *s, size_t s_len) +{ + while (s_len--) + iso7816_tx_program_putc(pio, sm, *s++); +} + +%}