first version producing a valid ATR recognized by CM3121

This commit is contained in:
Harald Welte 2023-04-01 20:17:49 +02:00
parent bc7a739f1c
commit bc41f7e710
4 changed files with 343 additions and 1 deletions

View File

@ -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)

View File

@ -0,0 +1,213 @@
#include <stdio.h>
#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]);
}
}

49
iso7816/iso7816_rx.pio Normal file
View File

@ -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)
}
%}

78
iso7816/iso7816_tx.pio Normal file
View File

@ -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++);
}
%}