diff --git a/ATMegaCard/ISO7816-card.c b/ATMegaCard/ISO7816-card.c new file mode 100644 index 0000000000000000000000000000000000000000..d82cf617a6eae3f444916639fa902a3c10a378cf --- /dev/null +++ b/ATMegaCard/ISO7816-card.c @@ -0,0 +1,345 @@ +#include "ISO7816-card.h" +#include "common.h" +#include <util/delay.h> +#include <string.h> + +//ATMega Card +//ATR: 3B8F014156522049534F3738313620302E31B9 +//Query: 00C101FE3E00000267016400C101FE3E00000267016400C101FE3E000002670164 + +//Java Card +//ATR: 3BFA1800FF8131FE454A434F5032315632333165 +//Query: FF1118F6FF1118F6F8FF38F8E098E0003BFA1800FF8131FE454A434F5032315632333165 +static apdu_handler_t handler; + +// TS T0 TA1 TB1 TC1 TD1(T=1) +ISO7816_ATR atr = {0x3B, 0x9F, {{0x11, 0x00, 0x00, 0x01}, +// TA2 TB2 TC2 TD2 (unused) + {0x00, 0x00, 0x00, 0x00}, +// TA3 TB3 TC3 TD3 (unused) + {0x00, 0x00, 0x00, 0x00}, +// TA4 TB4 TC4 TD4 (unused) + {0x00, 0x00, 0x00, 0x00}, +// TA5 TB5 TC5 TD5 (unused) + {0x00, 0x00, 0x00, 0x00}}, + //Historical Bytes + {0x41,0x56,0x52,0x20,0x49,0x53,0x4f,0x37,0x38,0x31,0x36,0x20,0x30,0x2e,0x31}, + 0}; //Checksum + +// unsigned char atr[] = {0x3B, 0xFA, 0x18, 0x00, 0xFF , 0x81 , 0x31 , 0xFE , 0x45 , 0x4A , 0x43 , 0x4F , 0x50 , 0x32 , 0x31 , 0x56 , 0x32 , 0x33 , 0x31, 0x65}; + +uint16_t ISO7816_receive_buffer(uint8_t* buffer, uint8_t length); +uint8_t ISO7816_transmit_buffer(uint8_t* buffer, uint8_t length); +uint8_t ISO7816_transmit_t1_header(ISO7816_T1_Header* t1_header); + +void ISO7816_init(void){ + UART_init(); +} +/* +void ISO7816_sendATR(void){ + UART_transmit_init(); + for (uint8_t i = 0; i < sizeof(atr); i++){ + UART_transmit_char(atr[i]); + } + UART_recv_init(); +}*/ + +void ISO7816_sendATR(void){ + uint8_t checksum = 0; + UART_transmit_init(); + UART_transmit_char(atr.initial_char); + checksum ^= atr.t0; UART_transmit_char(atr.t0); + + uint8_t num_hist_bytes = NUM_HIST_BYTES(atr); + uint8_t next_interface_bytes = INTERFACE_BYTE_SET(atr, 0); + uint8_t i = 0; + while (next_interface_bytes){ + if (next_interface_bytes & TA) {checksum ^= atr.interface_bytes[i].ta; UART_transmit_char(atr.interface_bytes[i].ta);} + if (next_interface_bytes & TB) {checksum ^= atr.interface_bytes[i].tb; UART_transmit_char(atr.interface_bytes[i].tb);} + if (next_interface_bytes & TC) {checksum ^= atr.interface_bytes[i].tc; UART_transmit_char(atr.interface_bytes[i].tc);} + if (next_interface_bytes & TD) {checksum ^= atr.interface_bytes[i].td; UART_transmit_char(atr.interface_bytes[i].td);} + i++; + next_interface_bytes = INTERFACE_BYTE_SET(atr, i); + } + for (i = 0; i < num_hist_bytes; i++){ + checksum ^= atr.historical_bytes[i]; + UART_transmit_char(atr.historical_bytes[i]); + } + UART_transmit_char(checksum); + UART_recv_init(); +// UART_read_char(); +} + +uint8_t ISO7816_data_available(void){ + return UART_data_available(); +} + +#define RECV_ERROR(new_status) {status = (status == ISO7816_STATUS_OK) ? new_status : status;} + +uint16_t process_pts(void){ + uint8_t checksum = 0xFF; //PTSS = 0xFF, so we start with that + uint16_t ret; + uint8_t c; + uint8_t pts0; + uint8_t pts1; + uint8_t pts2; + uint8_t pts3; + + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + pts0 = ret & 0xFF; + checksum ^= pts0; + + if (pts0 & 0x10){ //PTS1 avail + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + pts1 = ret & 0xFF; + checksum ^= pts1; + } + if (pts0 & 0x20){ //PTS2 avail + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + pts2 = ret & 0xFF; + checksum ^= pts2; + } + if (pts0 & 0x40){ //PTS3 avail + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + pts3 = ret & 0xFF; + checksum ^= pts3; + } + + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + c = ret & 0xFF; + checksum ^= c; + + if (c != checksum) return ISO7816_STATUS_ERR_DATA_INVALID; + _delay_us(300); + + UART_transmit_init(); + UART_transmit_char(0xFF); //PTSS + UART_transmit_char(pts0); + if (pts0 & 0x10) UART_transmit_char(pts1); + if (pts0 & 0x20) UART_transmit_char(pts2); + if (pts0 & 0x40) UART_transmit_char(pts3); + UART_transmit_char(checksum); + + UART_recv_init(); + + return ISO7816_STATUS_OK; +} + +uint8_t ISO7816_read_t1_header(ISO7816_T1_Header* h, uint8_t* checksum){ + uint16_t ret; + uint8_t c = 0; +// static uint8_t first = 0; + +// if (first){ +// while (c == 0x00){ +// //NAD (Node Address, not used) +// ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); +// if (ret == UART_ERR_TIMEOUT) return 0; +// c = ret & 0xFF; +// *checksum ^= c; +// if (c == 0xFF) {process_pts(); return 0;} +// } +// h->node_address = 0; +// +// first = 0; +// } else { + //NAD (Node Address, not used) + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return 0; + c = ret & 0xFF; + *checksum ^= c; + if (c == 0xFF) {process_pts(); return 0;} + h->node_address = c; + + // Protocol control byte + c = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return 0; + c = ret & 0xFF; + *checksum ^= c; + +// first = 1; +// } + + h->protocol_control = c; +// if (!(seq_number && (c & 0x40))) RECV_ERROR(ISO7816_STATUS_ERR_UNKNOWN); //TODO: Better response? Or no response? This is below the APDU level + + //Message Length + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return 0; + c = ret & 0xFF; + *checksum ^= c; + h->message_length = c; + + return 1; +} + +uint16_t ISO7816_read_t1_trailer(uint8_t checksum){ + uint16_t ret; + uint8_t c; + + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + c = ret & 0xFF; + +// if (c != checksum) return ISO7816_STATUS_ERR_DATA_INVALID; + + return 1; +} + +void ISO7816_process_apdu(ISO7816_T1_Header* t1_header, uint8_t* message_buffer){ + uint16_t status = ISO7816_STATUS_OK; + + ISO7816_APDU_Header *apdu_header = (ISO7816_APDU_Header*)&message_buffer[0]; + uint8_t* payload = &message_buffer[APDU_PAYLOAD_OFFSET]; + uint8_t payload_length = 0; + + uint8_t answer_buffer[255]; + uint8_t answer_length = 0; + uint8_t max_answer_length = 0; + + + //Now about interpreting length values... + if (t1_header->message_length < sizeof(ISO7816_APDU_Header)){ + status = ISO7816_STATUS_ERR_WRONG_LENGTH; + } else if (t1_header->message_length == sizeof(ISO7816_APDU_Header)){ + //no APDU payload, no answer payload. + payload_length = 0; + max_answer_length = 0; + } else if (t1_header->message_length == sizeof(ISO7816_APDU_Header) + 1){ + //no APDU payload, but answer payload allowed. + payload_length = 0; + max_answer_length = APDU_PAYLOAD_LENGTH(message_buffer); + } else { //we have APDU payload + payload_length = APDU_PAYLOAD_LENGTH(message_buffer); + if (t1_header->message_length - sizeof(ISO7816_APDU_Header) - 1 == payload_length){ + //APDU Header + APDU Payload, no answer payload + max_answer_length = 0; + } else if (t1_header->message_length - sizeof(ISO7816_APDU_Header) - 1 == (payload_length + 1)) { + //APDU Header + APDU Payload + answer payload + max_answer_length = message_buffer[t1_header->message_length - 1]; + } else { + status = ISO7816_STATUS_ERR_WRONG_LENGTH; + } + } + + + if (status == ISO7816_STATUS_OK){ + answer_length = max_answer_length; + status = handler(apdu_header, payload, payload_length, answer_buffer, &answer_length); + if (answer_length > max_answer_length){ + status = ISO7816_STATUS_ERR_WRONG_LENGTH; + } + } + if (status != ISO7816_STATUS_OK) answer_length = 0; + + t1_header->node_address = 0; + t1_header->message_length = answer_length+2; + + memcpy(message_buffer, answer_buffer, answer_length); + message_buffer[answer_length] = status >> 8; + message_buffer[answer_length+1] = status & 0xFF; +} + +uint8_t ISO7816_process(void){ + uint16_t ret; + + ISO7816_T1_Header t1_header; + + uint8_t ok; + uint8_t checksum = 0; + + uint8_t buffer[255]; + + ok = ISO7816_read_t1_header(&t1_header, &checksum); //T1 Protocol header + if (!ok) return 0; + + ret = ISO7816_receive_buffer(buffer, t1_header.message_length); //Message Body (e.g. APDU) + if (ret == UART_ERR_TIMEOUT) return 0; + checksum ^= ret & 0xFF; + + ok = ok && ISO7816_read_t1_trailer(checksum); //T1 Protocol trailer (checksum) + + //------------ Process message ------------ + if (ok){ + if (t1_header.protocol_control == ISO7816_PCB_IFS_REQUEST){ + t1_header.protocol_control = ISO7816_PCB_IFS_RESPONSE; + t1_header.node_address = 0; + + } else if (ISO7816_PCM_IS_I_BLOCK(t1_header.protocol_control)){ + ISO7816_process_apdu(&t1_header, buffer); + } else { + ok = 0; + } + } + +// PORTB |= (1 << 7); + + if (!ok) return 0; + + //------------ Send Answer ------------ + _delay_us(200); + PORTB &= ~(1 << 7); + + UART_transmit_init(); + _delay_us(200); + + checksum = 0; + checksum ^= ISO7816_transmit_t1_header(&t1_header); //Header + checksum ^= ISO7816_transmit_buffer(buffer, t1_header.message_length); //Payload + UART_transmit_char(checksum); //Checksum + + UART_recv_init(); //switch back to receive mode. + + return ok; +} + +uint8_t ISO7816_transmit_t1_header(ISO7816_T1_Header* t1_header){ + //Node Address TODO: WTF is that? do i need to care? + uint8_t checksum = t1_header->node_address; + UART_transmit_char(t1_header->node_address); + + //Protocol control byte + checksum ^= t1_header->protocol_control; //seq_number ? 0x40 : 0x00;; + UART_transmit_char(t1_header->protocol_control); + + //Answer length (answer payload + 2 byte Status) +// t1_header.message_length = answer_length + 2; + checksum ^= t1_header->message_length; + UART_transmit_char(t1_header->message_length); + + return checksum; +} + +uint8_t ISO7816_transmit_buffer(uint8_t* buffer, uint8_t length){ + uint8_t checksum = 0; + + for (uint8_t i = 0; i < length; i++){ + UART_transmit_char(buffer[i]); + checksum ^= buffer[i]; + } + return checksum; +} + +uint16_t ISO7816_receive_buffer(uint8_t* buffer, uint8_t length){ + uint8_t c; + uint8_t checksum = 0; + uint16_t ret; + + for (uint8_t i = 0; i < length; i++){ + ret = UART_receive_char_timeout(ISO7816_APDU_READ_TIMEOUT); + if (ret == UART_ERR_TIMEOUT) return ISO7816_ERR_TIMEOUT; + c = ret & 0xFF; + checksum ^= c; + if (buffer) buffer[i] = c; + } + return checksum; +} + +void ISO7816_set_apdu_handler(apdu_handler_t h){ + handler = h; +} \ No newline at end of file diff --git a/ATMegaCard/ISO7816-card.h b/ATMegaCard/ISO7816-card.h new file mode 100644 index 0000000000000000000000000000000000000000..9728a1370a57b9c2ca9b9be61d0b31398a5285b8 --- /dev/null +++ b/ATMegaCard/ISO7816-card.h @@ -0,0 +1,51 @@ +#pragma once + +#include "software_serial.h" +#include "../common/ISO7816-common.h" + +#define ISO7816_APDU_READ_TIMEOUT 3000 //300ms +#define ISO7816_ERR_TIMEOUT 0xFFFF + +#define ISO7816_STATUS_OK 0x9000 +#define ISO7816_STATUS_ERR_INCORRECT_P1P2 0x6A86 +#define ISO7816_STATUS_ERR_WRONG_P1P2 0x6B00 +#define ISO7816_STATUS_ERR_CORRECT_LENGTH_00 0x6C00 +#define ISO7816_STATUS_ERR_INS_NOT_SUPPORTED 0x6D00 +#define ISO7816_STATUS_ERR_CLA_NOT_SUPPORTED 0x6E00 +#define ISO7816_STATUS_ERR_BYTES_REMAINING_00 0x6100 +#define ISO7816_STATUS_ERR_WRONG_LENGTH 0x6700 +#define ISO7816_STATUS_ERR_SECURITY_STATUS_NOT_SATISFIED 0x6982 +#define ISO7816_STATUS_ERR_FILE_INVALID 0x6983 +#define ISO7816_STATUS_ERR_DATA_INVALID 0x6984 +#define ISO7816_STATUS_ERR_CONDITIONS_NOT_SATISFIED 0x6985 +#define ISO7816_STATUS_ERR_COMMAND_NOT_ALLOWED 0x6986 +#define ISO7816_STATUS_ERR_WRONG_DATA 0x6A80 +#define ISO7816_STATUS_ERR_FUNC_NOT_SUPPORTED 0x6A81 +#define ISO7816_STATUS_ERR_FILE_NOT_FOUND 0x6A82 +#define ISO7816_STATUS_ERR_RECORD_NOT_FOUND 0x6A83 +#define ISO7816_STATUS_ERR_APPLET_SELECT_FAILED 0x6999 +#define ISO7816_STATUS_ERR_UNKNOWN 0x6F00 + +struct ISO7816_T1_Header{ + uint8_t node_address; + uint8_t protocol_control; + uint8_t message_length; +} typedef ISO7816_T1_Header; + +#define ISO7816_PCM_IS_I_BLOCK(pcb) (((pcb) & 0x80) == 0) +#define ISO7816_PCB_IFS_REQUEST 0xC1 +#define ISO7816_PCB_IFS_RESPONSE 0xE1 + +#define MIN(a,b) (((a) < (b)) : (a) : (b)) + +//uint16_t ISO7816_handle_adpu(ISO7816_APDU_Header* header, uint8_t* payload, uint8_t payload_length, uint8_t* answer_buffer, uint8_t* answer_length); + +typedef uint16_t(*apdu_handler_t)(ISO7816_APDU_Header*, uint8_t*, uint8_t, uint8_t*, uint8_t*); + +void ISO7816_init(void); +void ISO7816_sendATR(void); +uint8_t ISO7816_data_available(void); +uint8_t ISO7816_process(void); +void ISO7816_set_apdu_handler(apdu_handler_t handler); + + diff --git a/ATMegaCard/Makefile b/ATMegaCard/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..cb0f90049378c8d8999afceb40a7ca7e8439b566 --- /dev/null +++ b/ATMegaCard/Makefile @@ -0,0 +1,398 @@ +# WinAVR Sample makefile written by Eric B. Weddington, Jörg Wunsch, et al. +# Released to the Public Domain +# Please read the make user manual! +# +# Additional material for this makefile was submitted by: +# Tim Henigan +# Peter Fleury +# Reiner Patommel +# Sander Pool +# Frederik Rouleau +# Markus Pfaff +# +# On command line: +# +# make all = Make software. +# +# make clean = Clean out built project files. +# +# make coff = Convert ELF to AVR COFF (for use with AVR Studio 3.x or VMLAB). +# +# make extcoff = Convert ELF to AVR Extended COFF (for use with AVR Studio +# 4.07 or greater). +# +# make program = Download the hex file to the device, using avrdude. Please +# customize the avrdude settings below first! +# +# make filename.s = Just compile filename.c into the assembler code only +# +# To rebuild project do "make clean" then "make all". +# + +# MCU name + +MCU = atmega163 + + + +#Fuse settings for ATmega163 +ifeq ($(MCU), atmega163) + FUSE_BITS = -u -U lfuse:w:0x81:m -U hfuse:w:0x01:m + HEX_FILE_NAME = MEGA163 +endif + +# Output format. (can be srec, ihex, binary) +FORMAT = ihex + +# Target file name (without extension). +TARGET = out/atmegacard_$(HEX_FILE_NAME) + +# Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization. +# (Note: 3 is not always the best optimization level. See avr-libc FAQ.) +OPT = s + +# If there is more than one source file, append them above, or modify and +# uncomment the following: + +SRC = main.c software_serial.c ISO7816-card.c keystore.c rng.c ../common/sha1/sha1.c applications/portal/portal.c + +# List Assembler source files here. +# Make them always end in a capital .S. Files ending in a lowercase .s +# will not be considered source files but generated files (assembler +# output from the compiler), and will be deleted upon "make clean"! +# Even though the DOS/Win* filesystem matches both .s and .S the same, +# it will preserve the spelling of the filenames, and gcc itself does +# care about how the name is spelled on its command-line. +ASRC = + + +# List any extra directories to look for include files here. +# Each directory must be seperated by a space. +EXTRAINCDIRS = + + +# Optional compiler flags. +# -g: generate debugging information (for GDB, or for COFF conversion) +# -O*: optimization level +# -f...: tuning, see gcc manual and avr-libc documentation +# -Wall...: warning level +# -Wa,...: tell GCC to pass this to the assembler. +# -ahlms: create assembler listing +CFLAGS = -g -O$(OPT) \ +-funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \ +-Wall -Wstrict-prototypes \ +-Wa,-adhlns=$(<:.c=.lst) \ +$(patsubst %,-I%,$(EXTRAINCDIRS)) + + +# Set a "language standard" compiler flag. +# Unremark just one line below to set the language standard to use. +# gnu99 = C99 + GNU extensions. See GCC manual for more information. +#CFLAGS += -std=c89 +#CFLAGS += -std=gnu89 +#CFLAGS += -std=c99 +CFLAGS += -std=gnu99 + + + +# Optional assembler flags. +# -Wa,...: tell GCC to pass this to the assembler. +# -ahlms: create listing +# -gstabs: have the assembler create line number information; note that +# for use in COFF files, additional information about filenames +# and function names needs to be present in the assembler source +# files -- see avr-libc docs [FIXME: not yet described there] +ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs + + + +# Optional linker flags. +# -Wl,...: tell GCC to pass this to linker. +# -Map: create map file +# --cref: add cross reference to map file +LDFLAGS = -Wl,-Map=$(TARGET).map,--cref + + + +# Additional libraries + +# Minimalistic printf version +#LDFLAGS += -Wl,-u,vfprintf -lprintf_min + +# Floating point printf version (requires -lm below) +#LDFLAGS += -Wl,-u,vfprintf -lprintf_flt + +# -lm = math library +LDFLAGS += -lm + + + + +# Programming support using avrdude. Settings and variables. + +# Programming hardware: alf avr910 avrisp bascom bsd +# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500 +# +# Type: avrdude -c ? +# to get a full listing. +# + +#AVRDUDE_PROGRAMMER = AVR910 +AVRDUDE_PROGRAMMER = usbtiny +# AVRDUDE_PROGRAMMER = USBasp + +AVRDUDE_PORT = usb # programmer connected to USB port +#AVRDUDE_PORT = com1 # programmer connected to serial device +#AVRDUDE_PORT = lpt1 # programmer connected to parallel port + +AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex $(FUSE_BITS) +#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep + +#AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -b 115200 -c $(AVRDUDE_PROGRAMMER) +AVRDUDE_FLAGS = -B 1 -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) + +# Uncomment the following if you want avrdude's erase cycle counter. +# Note that this counter needs to be initialized first using -Yn, +# see avrdude manual. +#AVRDUDE_ERASE += -y + +# Uncomment the following if you do /not/ wish a verification to be +# performed after programming the device. +#AVRDUDE_FLAGS += -V + +# Increase verbosity level. Please use this when submitting bug +# reports about avrdude. See <http://savannah.nongnu.org/projects/avrdude> +# to submit bug reports. +#AVRDUDE_FLAGS += -v -v + + + + +# --------------------------------------------------------------------------- + +# Define programs and commands. +SHELL = sh + +CC = avr-gcc + +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +SIZE = avr-size + + +# Programming support using avrdude. +AVRDUDE = avrdude + + +REMOVE = rm -f +COPY = cp + +HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex +ELFSIZE = $(SIZE) -A $(TARGET).elf + + + +# Define Messages +# English +MSG_ERRORS_NONE = Errors: none +MSG_BEGIN = -------- begin -------- +MSG_END = -------- end -------- +MSG_SIZE_BEFORE = Size before: +MSG_SIZE_AFTER = Size after: +MSG_COFF = Converting to AVR COFF: +MSG_EXTENDED_COFF = Converting to AVR Extended COFF: +MSG_FLASH = Creating load file for Flash: +MSG_EEPROM = Creating load file for EEPROM: +MSG_EXTENDED_LISTING = Creating Extended Listing: +MSG_SYMBOL_TABLE = Creating Symbol Table: +MSG_LINKING = Linking: +MSG_COMPILING = Compiling: +MSG_ASSEMBLING = Assembling: +MSG_CLEANING = Cleaning project: + + + + +# Define all object files. +OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) + +# Define all listing files. +LST = $(ASRC:.S=.lst) $(SRC:.c=.lst) + +# Combine all necessary flags and optional flags. +# Add target processor to flags. +ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) +ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) + + + +# Default target. +all: begin gccversion sizebefore $(TARGET).elf $(TARGET).hex $(TARGET).eep \ + $(TARGET).lss $(TARGET).sym sizeafter finished end + + +# Eye candy. +# AVR Studio 3.x does not check make's exit code but relies on +# the following magic strings to be generated by the compile job. +begin: + @echo + @echo $(MSG_BEGIN) + +finished: + @echo $(MSG_ERRORS_NONE) + +end: + @echo $(MSG_END) + @echo + + +# Display size of file. +sizebefore: + @if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); echo; fi + +sizeafter: + @if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); echo; fi + + + +# Display compiler version information. +gccversion : + @$(CC) --version + + + + +# Convert ELF to COFF for use in debugging / simulating in +# AVR Studio or VMLAB. +COFFCONVERT=$(OBJCOPY) --debugging \ + --change-section-address .data-0x800000 \ + --change-section-address .bss-0x800000 \ + --change-section-address .noinit-0x800000 \ + --change-section-address .eeprom-0x810000 + + +coff: $(TARGET).elf + @echo + @echo $(MSG_COFF) $(TARGET).cof + $(COFFCONVERT) -O coff-avr $< $(TARGET).cof + + +extcoff: $(TARGET).elf + @echo + @echo $(MSG_EXTENDED_COFF) $(TARGET).cof + $(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof + + + + +# Program the device. +program: $(TARGET).hex $(TARGET).eep + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM) + + + + +# Create final output files (.hex, .eep) from ELF output file. +%.hex: %.elf + @echo + @echo $(MSG_FLASH) $@ + $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +%.eep: %.elf + @echo + @echo $(MSG_EEPROM) $@ + -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ + --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ + +# Create extended listing file from ELF output file. +%.lss: %.elf + @echo + @echo $(MSG_EXTENDED_LISTING) $@ + $(OBJDUMP) -h -S $< > $@ + +# Create a symbol table from ELF output file. +%.sym: %.elf + @echo + @echo $(MSG_SYMBOL_TABLE) $@ + avr-nm -n $< > $@ + + + +# Link: create ELF output file from object files. +.SECONDARY : $(TARGET).elf +.PRECIOUS : $(OBJ) +%.elf: $(OBJ) + @echo + @echo $(MSG_LINKING) $@ + $(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS) + + +# Compile: create object files from C source files. +%.o : %.c + @echo + @echo $(MSG_COMPILING) $< + $(CC) -c $(ALL_CFLAGS) $< -o $@ + + +# Compile: create assembler files from C source files. +%.s : %.c + $(CC) -S $(ALL_CFLAGS) $< -o $@ + + +# Assemble: create object files from assembler source files. +%.o : %.S + @echo + @echo $(MSG_ASSEMBLING) $< + $(CC) -c $(ALL_ASFLAGS) $< -o $@ + + + + + + +# Target: clean project. +clean: begin clean_list finished end + +clean_list : + @echo + @echo $(MSG_CLEANING) +# $(REMOVE) $(TARGET).hex + $(REMOVE) $(TARGET).eep + $(REMOVE) $(TARGET).obj + $(REMOVE) $(TARGET).cof + $(REMOVE) $(TARGET).elf + $(REMOVE) $(TARGET).map + $(REMOVE) $(TARGET).obj + $(REMOVE) $(TARGET).a90 + $(REMOVE) $(TARGET).sym + $(REMOVE) $(TARGET).lnk + $(REMOVE) $(TARGET).lss + $(REMOVE) $(OBJ) + $(REMOVE) $(LST) + $(REMOVE) $(SRC:.c=.s) + $(REMOVE) $(SRC:.c=.d) + + +# Automatically generate C source code dependencies. +# (Code originally taken from the GNU make user manual and modified +# (See README.txt Credits).) +# +# Note that this will work with sh (bash) and sed that is shipped with WinAVR +# (see the SHELL variable defined above). +# This may not work with other shells or other seds. +# +%.d: %.c + set -e; $(CC) -MM $(ALL_CFLAGS) $< \ + | sed 's,\(.*\)\.o[ :]*,\1.o \1.d : ,g' > $@; \ + [ -s $@ ] || rm -f $@ + + +# Remove the '-' if you want to see the dependency files generated. +-include $(SRC:.c=.d) + + + +# Listing of phony targets. +.PHONY : all begin finish end sizebefore sizeafter gccversion coff extcoff \ + clean clean_list program + diff --git a/ATMegaCard/applications/applications.h b/ATMegaCard/applications/applications.h new file mode 100644 index 0000000000000000000000000000000000000000..b0bd87c979c21439a79d345ca86b3912abb07ad6 --- /dev/null +++ b/ATMegaCard/applications/applications.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../ISO7816-card.h" + +typedef uint8_t application_id_t[5]; + +struct application_t{ + application_id_t aid; + void (*init)(void); + apdu_handler_t (*select)(void); + void (*deselect)(void); +} typedef application_t; + + +/* ****** Place application defined here ****** */ + +#include "portal/portal.h" +#define NUM_APPLICATIONS 1 +#define APPLICATIONS {APP_PORTAL} + +void portal_init(void); +apdu_handler_t portal_select(void); +void portal_deselect(void); + diff --git a/ATMegaCard/applications/portal/portal.c b/ATMegaCard/applications/portal/portal.c new file mode 100644 index 0000000000000000000000000000000000000000..2e1ed6af6c1e02729fa22d6f3ec40a96033d257a --- /dev/null +++ b/ATMegaCard/applications/portal/portal.c @@ -0,0 +1,126 @@ +#include <string.h> +#include "portal.h" +#include "../../ISO7816-card.h" +#include "keystore.h" +#include "../../rng.h" + +#define PORTAL_INS_QUERY_VERSION 0x00 +#define PORTAL_INS_INIT_KEY 0x01 +#define PORTAL_INS_AUTH 0x02 +#define PORTAL_INS_UPDATE_KEY 0x03 + +uint16_t portal_handle_adpu(ISO7816_APDU_Header* header, + uint8_t* payload, + uint8_t payload_length, + uint8_t* answer_buffer, + uint8_t* answer_length); + +sha1_ctx_t sha1_state; +uint8_t challenge[sizeof(SALT)]; +uint8_t current_auth_key = 1; + +void portal_init(void){ + rnd_get_bytes(challenge, sizeof(SALT)); +} + +apdu_handler_t portal_select(void){ + return portal_handle_adpu; +} + +uint16_t portal_handle_adpu(ISO7816_APDU_Header* header, + uint8_t* payload, + uint8_t payload_length, + uint8_t* answer_buffer, + uint8_t* answer_length){ + + switch (header->instruction){ + case PORTAL_INS_QUERY_VERSION: + if (*answer_length < 1) return ISO7816_STATUS_ERR_WRONG_LENGTH; + + *answer_length = 1; + answer_buffer[0] = 2; + return ISO7816_STATUS_OK; + break; + + + case PORTAL_INS_INIT_KEY: + if (payload_length != sizeof(KEY)) return ISO7816_STATUS_ERR_WRONG_LENGTH + payload_length+1; + + *answer_length = 0; + if (keystore_is_empty()){ + keystore_init_key(payload); + return ISO7816_STATUS_OK; + } else { + return ISO7816_STATUS_ERR_CONDITIONS_NOT_SATISFIED; + } + break; + + + case PORTAL_INS_AUTH: + if (*answer_length < sizeof(HASH) + sizeof(SALT)) return ISO7816_STATUS_ERR_WRONG_LENGTH; + if (payload_length != sizeof(SALT)) return ISO7816_STATUS_ERR_WRONG_LENGTH; + + current_auth_key = !current_auth_key; + *answer_length = sizeof(HASH) + sizeof(SALT); + sha1_init(&sha1_state); + sha1_nextBlock(&sha1_state, keystore_get_key(current_auth_key)); + sha1_lastBlock(&sha1_state, payload, sizeof(SALT)*8); + sha1_ctx2hash(answer_buffer, &sha1_state); + memcpy(answer_buffer + sizeof(HASH), challenge, sizeof(SALT)); + + return ISO7816_STATUS_OK; + break; + + + case PORTAL_INS_UPDATE_KEY: + if (payload_length != sizeof(KEY)+ sizeof(HASH)) return ISO7816_STATUS_ERR_WRONG_LENGTH; + *answer_length = 0; + + uint8_t* new_key_delta = payload; + uint8_t* challange_response = payload + sizeof(KEY); + + sha1_init(&sha1_state); + sha1_nextBlock(&sha1_state, keystore_get_key(current_auth_key)); + sha1_lastBlock(&sha1_state, challenge, sizeof(SALT)*8); + if (! sha1_ctx_hash_compare(challange_response, &sha1_state)){ + return ISO7816_STATUS_ERR_SECURITY_STATUS_NOT_SATISFIED; +// *answer_length = 20; +// memcpy(answer_buffer, (const void*)challange_response, 20); +// return ISO7816_STATUS_OK; + } + + + keystore_update_key(new_key_delta, current_auth_key); + + return ISO7816_STATUS_OK; + break; + + + case 0xFE: + if (*answer_length < sizeof(KEY)) return ISO7816_STATUS_ERR_WRONG_LENGTH; + + memset(answer_buffer, 0xBB, sizeof(KEY)); + memcpy(answer_buffer, keystore_get_key(0), sizeof(KEY)); + if (*answer_length < 2*sizeof(KEY)){ + *answer_length = sizeof(KEY); + } else { + memcpy(answer_buffer + sizeof(KEY), keystore_get_key(1), sizeof(KEY)); + *answer_length = 2*sizeof(KEY); + } + return ISO7816_STATUS_OK; + break; + + + case 0xFF: + *answer_length = 0; + keystore_clear(); + + return ISO7816_STATUS_OK; + break; + } + return ISO7816_STATUS_ERR_INS_NOT_SUPPORTED; +} + +void portal_deselect(void){ + +} diff --git a/ATMegaCard/applications/portal/portal.h b/ATMegaCard/applications/portal/portal.h new file mode 100644 index 0000000000000000000000000000000000000000..3a8f57416491f23e97e6429da02699cc6ff498f4 --- /dev/null +++ b/ATMegaCard/applications/portal/portal.h @@ -0,0 +1,7 @@ +#include "../applications.h" + +void portal_init(void); +apdu_handler_t portal_select(void); +void portal_deselect(void); + +#define APP_PORTAL {"\x23\x23\x00\x42\x42", portal_init, portal_select, portal_deselect} \ No newline at end of file diff --git a/ATMegaCard/common.h b/ATMegaCard/common.h new file mode 100644 index 0000000000000000000000000000000000000000..3c7f2176f8ddc22aaf14b6dcc314ec79d6607f7a --- /dev/null +++ b/ATMegaCard/common.h @@ -0,0 +1 @@ +#define F_CPU 3579500 \ No newline at end of file diff --git a/ATMegaCard/keystore.c b/ATMegaCard/keystore.c new file mode 100644 index 0000000000000000000000000000000000000000..b23e9e3529fb5e80bdb8fa9afc4dca46bd3b1df7 --- /dev/null +++ b/ATMegaCard/keystore.c @@ -0,0 +1,77 @@ +/** + * This Keystore manages two keyslots, a "primary" and a "secondary" slot + * The key locations (0 and 1) are at EEPROM location EEP_KEY_OFFSET and EEP_KEY_OFFSET+sizeof(KEY) + * The value at EEP_PRIMARY_KEY_SLOT declares which keyslot (0 or 1) ist the "primary" slot. + * The remaining one is the "secondary" + * + * If the "primary" key is requested, the value at EEP_PRIMARY_KEY_SLOT is looked up and the key is read from the indicated location + * If the "secondary" key is requested the key is read from the alternate location. + * + * When the "primary" key is updated (parameter "update_secondary" = false) the "secondary" keyslot is overwritten + * with the new key and then declared as "primary" by flipping the value stored at EEP_PRIMARY_KEY_SLOT. + * This procedure ensures that there is always one valid key in the store. + * + * When the "secondary" key is updated (parameter "update_secondary" = false) the "primary" keyslot is overwritten + * with the new key. The value at EEP_PRIMARY_KEY_SLOT stays unchanged, declaring the new key as "primary". + * The old secondary key stays at place. + * This procedure ensures that there is always one valid key in the store. + */ + +#include "keystore.h" + + +#define EEP_PRIMARY_KEY_SLOT 127 +#define EEP_KEY_OFFSET 128 + +static uint8_t get_keyslot_index(uint8_t secondary); +static void write_new_primary_key(uint8_t* key, uint8_t slotindex); +void xor(uint8_t* src1dst, uint8_t* src2, uint8_t length); + +uint8_t keybuffer[sizeof(KEY)]; + +uint8_t keystore_is_empty(void){ + return (eeprom_read_byte((const uint8_t*)EEP_PRIMARY_KEY_SLOT) == 0xFF); +} +uint8_t* keystore_get_key(int8_t secondary){ + uint8_t slotindex = get_keyslot_index(secondary); + + eeprom_read_block(keybuffer, (const void*)EEP_KEY_OFFSET + (slotindex*sizeof(KEY)), sizeof(KEY)); + return keybuffer; +} + +void keystore_init_key(uint8_t* key){ + uint8_t slotindex = get_keyslot_index(0); + write_new_primary_key(key, slotindex); +} + +void keystore_update_key(uint8_t* new_key_delta, uint8_t update_secondary){ + uint8_t read_slotindex = get_keyslot_index(update_secondary); //read from the valid slot + + eeprom_read_block(keybuffer, (const void*)EEP_KEY_OFFSET + (read_slotindex*sizeof(KEY)), sizeof(KEY)); + xor(keybuffer, new_key_delta, sizeof(KEY)); + + uint8_t write_slotindex = get_keyslot_index(!update_secondary); //write to the invalid slot + write_new_primary_key(keybuffer, write_slotindex); +} + +void keystore_clear(void){ + for (uint8_t i = 0; i < sizeof(KEY)*2; i++){ + eeprom_write_byte((uint8_t*)(EEP_KEY_OFFSET+i), 0xFF); + } + eeprom_write_byte((uint8_t*)EEP_PRIMARY_KEY_SLOT, 0xFF); +} + +static uint8_t get_keyslot_index(uint8_t secondary){ + uint8_t primary_slot = eeprom_read_byte((const uint8_t*)EEP_PRIMARY_KEY_SLOT); + if (primary_slot > 1) primary_slot = 0; + return secondary ? !primary_slot : primary_slot; +} +static void write_new_primary_key(uint8_t* key, uint8_t slotindex){ + eeprom_write_block(key, (void*)EEP_KEY_OFFSET + (slotindex*sizeof(KEY)) , sizeof(KEY)); + eeprom_write_byte((uint8_t*)EEP_PRIMARY_KEY_SLOT, slotindex); +} +void xor(uint8_t* src1dst, uint8_t* src2, uint8_t length){ + for (uint8_t i = 0; i < length; i++){ + src1dst[i] ^= src2[i]; + } +} diff --git a/ATMegaCard/keystore.h b/ATMegaCard/keystore.h new file mode 100644 index 0000000000000000000000000000000000000000..1b52a7e617e9b8593938cfa79b36b5336016b697 --- /dev/null +++ b/ATMegaCard/keystore.h @@ -0,0 +1,31 @@ +#include <avr/eeprom.h> +#include "../common/card_common.h" + +/** + * Returns non-zero result if the keystore is empty. + * Returns zero if at least one key has been stored. + */ +uint8_t keystore_is_empty(void); + +/** + * Initializes the "primary" key of the keystore + */ +void keystore_init_key(uint8_t* key); + +/** + * Loads a key to the keybuffer and returns the pointer to it. The pointer is valid until the next call to a keystore_ method. + * @param secondary 0 - load the primary key, 1 - load the secondary key + */ +uint8_t* keystore_get_key(int8_t secondary); + +/** + * Update a key with the given key_delta. + * @param new_key_delta bitwise XOR between the old key and the given key_delta is performed and the result is stored. + * @param update_secondary 0 - update the primary key (and mark it as primary) 1 - update the secondary key (and mark it as primary) + */ +void keystore_update_key(uint8_t* new_key_delta, uint8_t update_secondary); + +/** + * Delete all keys from the keystore + */ +void keystore_clear(void); \ No newline at end of file diff --git a/ATMegaCard/main.c b/ATMegaCard/main.c new file mode 100644 index 0000000000000000000000000000000000000000..270701fc18c12999f93e699992481e7e1494f143 --- /dev/null +++ b/ATMegaCard/main.c @@ -0,0 +1,77 @@ +#include "common.h" +#include <avr/io.h> +#include <util/delay.h> +#include <string.h> +#include "rng.h" + +#include "ISO7816-card.h" +#include "applications/applications.h" + +#define INS_APPLICATION_MANAGER 0xA4 +#define APPLICATION_MANAGER_P1_SELECT 0x04 + +application_t applications[NUM_APPLICATIONS] = APPLICATIONS; + +uint16_t main_handle_adpu(ISO7816_APDU_Header* header, + uint8_t* payload, + uint8_t payload_length, + uint8_t* answer_buffer, + uint8_t* answer_length); + +uint8_t current_application = 0xFF; + +int main(void){ + //DDRB = (1 << 5) | (1 << 7); +// ISO7816_init(); + ISO7816_set_apdu_handler(main_handle_adpu); + ISO7816_sendATR(); + sei(); + rnd_init(); + + + for (uint8_t i = 0; i < NUM_APPLICATIONS; i++){ + applications[i].init(); + } + + while(1){ + if (ISO7816_data_available()){ + ISO7816_process(); + } + } +} + +uint16_t main_handle_adpu(ISO7816_APDU_Header* header, + uint8_t* payload, + uint8_t payload_length, + uint8_t* answer_buffer, + uint8_t* answer_length){ + + *answer_length = 0; + + + if (header->instruction == INS_APPLICATION_MANAGER){ + + if (header->param1 == APPLICATION_MANAGER_P1_SELECT){ + for (uint8_t i = 0; i < NUM_APPLICATIONS; i++){ + if (memcmp(applications[i].aid, payload, sizeof(application_id_t)) == 0){ + if (current_application != 0xFF) applications[current_application].deselect(); + ISO7816_set_apdu_handler(applications[i].select()); + return APDU_RESULT_OK; + } + } + return ISO7816_STATUS_ERR_FILE_NOT_FOUND; + } else { + return ISO7816_STATUS_ERR_WRONG_P1P2; + } + } else if (header->instruction == 0x11){ + for (uint8_t i = 0; i < 32; i++){ + answer_buffer[i] = rnd_get_byte(); + } + *answer_length = 32; + return APDU_RESULT_OK; + + } else { + return ISO7816_STATUS_ERR_INS_NOT_SUPPORTED; + } + +} \ No newline at end of file diff --git a/ATMegaCard/rnd b/ATMegaCard/rnd new file mode 100644 index 0000000000000000000000000000000000000000..d36f2af328a4c177221926377bd8c3d53dd2880d Binary files /dev/null and b/ATMegaCard/rnd differ diff --git a/ATMegaCard/rng.c b/ATMegaCard/rng.c new file mode 100644 index 0000000000000000000000000000000000000000..de843640f2286f66443b996d69cb7c1f205ccfea --- /dev/null +++ b/ATMegaCard/rng.c @@ -0,0 +1,45 @@ +#include <avr/io.h> +#include <util/crc16.h> + +#define ADC_RNG_CHANNEL 0 + +uint16_t seed; + +uint8_t rnd_adc_read(void); + +void rnd_init(void){ + seed = 0xFFFF; + seed = _crc16_update(seed, rnd_adc_read()); + seed = _crc16_update(seed, rnd_adc_read()); +} + +uint8_t rnd_adc_read(void){ + uint8_t result = 0; + + ADMUX = ADC_RNG_CHANNEL | (1 << REFS0); // Select channel, AVCC Reference voltage. + + ADCSR = (1<<ADEN) | (1 << ADPS1); // Enable ADC (Prescaler 4) + +// for (uint8_t i = 0; i < 8; i++){ + ADCSR |= (1<<ADSC); + while (ADCSR & (1<<ADSC)) {;} +// +// result = (ADCL & 1) << i; +// ADCH; +// } + result = ADCL; + ADCH; + + return result; +} + +uint8_t rnd_get_byte(void){ + seed = _crc16_update(seed, rnd_adc_read()); + return ((seed & 0xFF) ^ (seed >> 8)); +} + +void rnd_get_bytes(uint8_t* buffer, uint8_t length){ + for (uint8_t i = 0; i < length; i++){ + buffer[i] = rnd_get_byte(); + } +} \ No newline at end of file diff --git a/ATMegaCard/rng.h b/ATMegaCard/rng.h new file mode 100644 index 0000000000000000000000000000000000000000..c309ffe529b6e9f65eab38640d80669cd06237bd --- /dev/null +++ b/ATMegaCard/rng.h @@ -0,0 +1,3 @@ +void rnd_init(void); +uint8_t rnd_get_byte(void); +void rnd_get_bytes(uint8_t* buffer, uint8_t length); \ No newline at end of file diff --git a/ATMegaCard/software_serial.c b/ATMegaCard/software_serial.c new file mode 100644 index 0000000000000000000000000000000000000000..bd9fb3946118f7ad442fc8d63f10c6f7c4a5fc23 --- /dev/null +++ b/ATMegaCard/software_serial.c @@ -0,0 +1,221 @@ +#include "software_serial.h" + +#ifdef UART_USE_AS_STDOUT //Sets up the serial port as a stream for stdout. With this printf and stuff will write to the serial port. + #include <stdio.h> + + void UART_transmit_stdout(char c, FILE* stream){ + UART_transmit_char((unsigned char)c); + } + + static FILE UART_stdout = FDEV_SETUP_STREAM(UART_transmit_stdout, NULL, _FDEV_SETUP_WRITE); +#endif + + +/** + * Initializes the Software UART + */ +void UART_init(void){ + #ifdef UART_USE_AS_STDOUT + stdout = &UART_stdout; //assign serial output stream as stdout. + #endif + + UART_recv_init(); +} + +/** + * Switches the UART to transmit mode + */ +void UART_transmit_init(void){ + cli(); //interrupts disable (messes up send timings otherwise) + SWSERIAL_PORT |= (1 << SWSERIAL_LINE); //data line to high level (otherwise we might trigger a start bit) + SWSERIAL_PORT_DR |= (1 << SWSERIAL_LINE); //output +} + +/** + * Sends a 0-terminated string + * Note: The caller must to switch to transmit mode before calling this + */ +void UART_transmit_buffer_sz(char* buffer){ + while (buffer[0] != 0){ + UART_transmit_char(buffer[0]); + buffer++; + } +} + +/** + * Sends a byte coverted to hex digits + * Note: The caller must to switch to transmit mode before calling this + */ +void UART_transmit_byte_hex(unsigned char b){ + + unsigned char n; + n = '0' + (b >> 4 & 0xF); + UART_transmit_char((n > '9') ? n + 7 : n); + n = '0' + (b & 0xF); + UART_transmit_char((n > '9') ? n + 7 : n); +} + + +/** + * Writes a single character to the data line, bit-by-bit + * Note: The caller must to switch to transmit mode before calling this + */ +void UART_transmit_char(unsigned char x){ + + _delay_us(BIT_LENGTH); //guard time beween bytes + + WRITE(0); //start bit + + #if SWSERIAL_PARITY == EVEN + uint8_t parity = 0; + #endif + #if SWSERIAL_PARITY == ODD + uint8_t parity = 1; + #endif + for (unsigned char i = 0; i < 8; i++){ + _delay_us(BIT_LENGTH - INST_LENGTH(8)); + WRITE(x & 1); + #if SWSERIAL_PARITY != NONE + parity ^= x & 1; + #endif + x >>= 1; + } + + #if (SWSERIAL_PARITY != NONE) + _delay_us(BIT_LENGTH - INST_LENGTH(8)); + WRITE(parity); + #endif + + _delay_us(BIT_LENGTH - INST_LENGTH(8)); + WRITE(1); //stop bit + + #if SWSERIAL_STOPBITS == 2 + _delay_us(BIT_LENGTH - INST_LENGTH(8)); + WRITE(1); //stop bit 2 + #endif + + _delay_us(BIT_LENGTH); //guard time beween bytes + _delay_us(BIT_LENGTH); //guard time beween bytes +} + + +//------------------------- Receive code ---------------------------- + +uint8_t recv_buffer1 = 0; //Byte currently beeing received (only used by timer interrupt handler) +uint8_t recv_bits = 0; //Number of bits done in current receive (only used by timer interrupt handler) +volatile uint8_t recv_buffer2 = 0; //Last Byte that finished receiving +volatile uint8_t recv_avail = 0; //Is a byte ready to be processed? +volatile uint8_t recv_parity; +volatile uint8_t recv_valid = 0; //Did we receive 1 for the stop bits and valid parity + +/** + * Switch UART into receive mode + */ +void UART_recv_init(void){ + SWSERIAL_PORT_DR &= ~(1 << SWSERIAL_LINE); //set Serial pin to input + SWSERIAL_PORT |= (1 << SWSERIAL_LINE); //pullup + + DDRB |= (1 << 5) | (1 << 7); + + TIFR = (1 << ICF1); //clear old interrupt flag. + TIMSK = 1 << TICIE1; //enable ICP interrupt, disable timer compare interrupt + TCCR1B = (1 << CS10) //Timer prescaler = 1 +// #ifdef CTC1 + | (1 << CTC1) //Clear timer on compare match (important for exact timing, don't do it in software) +// #else +// | (1 << WGM12) +// #endif + | (1 << ICNC1) //Capture interrupt noise canceler enable (filters out very short spikes < 4 clks) +// #ifdef ICES1 +// | (1 << ICES1) +// #endif + ; + + OCR1A = 372; //Default smartcard conversion factor 3571200Hz/372(clks/bit) = 9600bit/s + + sei(); //global interrupt enable +} + +/** + * Is a byte available to be processed? + */ +inline uint8_t UART_data_available(void){ + return recv_avail != 0; +} +/** + * Reads the last byte that was received. + * Resets the availability flag. + */ +uint8_t UART_read_char(void){ + recv_avail = 0; + return recv_buffer2; +} + +uint16_t UART_receive_char_timeout(uint16_t timeout){ + while ((timeout > 0) && (recv_avail == 0)) {_delay_us(100); timeout--;} + if (recv_avail == 0) return UART_ERR_TIMEOUT; + + return UART_read_char(); +} + +/** + * Enables the timer (interrupt) that samples the data line to receive the bits. + * Disables the Capture (ICP) interrupt that triggered on the start bit. + */ +void UART_start_read(void){ + recv_bits = 0; + + #if SWSERIAL_PARITY == EVEN + recv_parity = 0; + #endif + #if SWSERIAL_PARITY == ODD + recv_parity = 1; + #endif + + TCNT1 = 0xFFFF-70; //Timer value. We start above the match counter, instead of starting af 0, this makes the first tick take a little longer, moving the sampling further inside the bit, not at the very beginning. + + TIFR = (1 << OCF1A); //clear old interrupt flag + TIMSK = (1 << OCIE1A); //Disable ICP interrupt, Enable timer Compare Interrupt. +} + +/** + * Capture interrupt, trigered by a falling edge on the data line (start bit) + */ +ISR(TIMER1_CAPT_vect){ + PORTB |= (1 << 5); //Debug + UART_start_read(); + PORTB &= ~(1 << 5); //Debug +} + +/** + * Timer interrupt, samples the data line and writes the bits to the recv buffer. + * If all bits are read it returns control to the Capture interrupt and disables itself. + */ +ISR(TIMER1_COMPA_vect){ + PORTB |= (1 << 7); //Debug + + uint8_t bit = (SWSERIAL_PIN & (1 << SWSERIAL_LINE)) ? 1 : 0; + + if (recv_bits < 8){ + recv_buffer1 >>= 1; + recv_buffer1 |= (bit ? 0x80 : 0); + if (bit) recv_parity = !recv_parity; + } + if (recv_bits == 8){ + recv_valid = bit == recv_parity; + } + if (recv_bits == 9){ + if (!bit) recv_valid = 0; + } + + recv_bits++; + if (recv_bits == 10){ + if (recv_valid){ + recv_buffer2 = recv_buffer1; + recv_avail = 1; + } + recv_bits = 0; + UART_recv_init(); + } + PORTB &= ~(1 << 7); //Debug +} diff --git a/ATMegaCard/software_serial.h b/ATMegaCard/software_serial.h new file mode 100644 index 0000000000000000000000000000000000000000..e7a423a9ac21d818acbf0493e2beafce84ae69df --- /dev/null +++ b/ATMegaCard/software_serial.h @@ -0,0 +1,58 @@ +#include "common.h" +#include <avr/io.h> +#include <util/delay_basic.h> +#include <util/delay.h> +#include <avr/interrupt.h> + +//Serial Data Line: PD6 (ICP) + +#define BAUD 9600 +#define SWSERIAL_PARITY EVEN +#define SWSERIAL_STOPBITS 2 +#define SWSERIAL_PORT_NAME D +#define SWSERIAL_LINE 6 + +#define UART_ERR_TIMEOUT 0xFFFF + +#ifndef XPASTE + //Macro magic to glue #defined names together. See http://groups.google.com/group/comp.lang.c/msg/8c085fe5a8ad1296?hl=en + #define PASTE(a,b) a ## b + #define XPASTE(a,b) PASTE(a,b) +#endif + +#define EVEN 1 +#define ODD 2 +#define NONE 0 +#ifndef SWSERIAL_PARITY + #define SWSERIAL_PARITY NONE +#endif +#ifndef SWSERIAL_STOPBITS + #define SWSERIAL_STOPBITS 1 +#endif + +#define BIT_LENGTH 1000000/BAUD //(µSec) Duration of a RS232 Bit + +#define SWSERIAL_PORT_DR XPASTE(DDR, SWSERIAL_PORT_NAME) +#define SWSERIAL_PORT XPASTE(PORT,SWSERIAL_PORT_NAME) +#define SWSERIAL_PIN XPASTE(PIN, SWSERIAL_PORT_NAME) + +#define WRITE(x) if ((x) != 0) {SWSERIAL_PORT |= (1 << SWSERIAL_LINE);} else {SWSERIAL_PORT &= (unsigned char)~(1 << SWSERIAL_LINE);} + + +#define INST_LENGTH(count) ((1000000*count)/F_CPU) //(µSec) Duration of count CPU Instructions + +void UART_transmit_char(unsigned char data); + +void UART_transmit_buffer_sz(char* buffer); +void UART_transmit_byte_hex(unsigned char b); + +void UART_init(void); + +void UART_transmit_init(void); +void UART_recv_init(void); + +void UART_transmit_char(unsigned char x); + +uint8_t UART_data_available(void); +uint8_t UART_read_char(void); +uint16_t UART_receive_char_timeout(uint16_t timeout); \ No newline at end of file