osmo-ccid-firmware/sysmoOCTSIM/main.c

590 lines
15 KiB
C

/*
* Copyright (C) 2019 sysmocom -s.f.m.c. GmbH, Author: Kevin Redon <kredon@sysmocom.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include <parts.h>
#include <errno.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/timer.h>
#include <hal_cache.h>
#include <hri_port_e54.h>
#include "atmel_start.h"
#include "atmel_start_pins.h"
#include "config/hpl_gclk_config.h"
#include "i2c_bitbang.h"
#include "octsim_i2c.h"
#include "ncn8025.h"
#include "iso7816_3.h"
#include "command.h"
#include "ccid_device.h"
#include "usb_descriptors.h"
extern struct ccid_slot_ops iso_fsm_slot_ops;
static struct ccid_instance g_ci;
static void ccid_app_init(void);
static void board_init()
{
int i;
for (i = 0; i < 4; i++)
i2c_init(&i2c[i]);
for (i = 0; i < 8; i++)
ncn8025_init(i);
cache_init();
cache_enable(CMCC);
calendar_enable(&CALENDAR_0);
/* increase drive strength of 20Mhz SIM clock output to 8mA
* (there are 8 inputs + traces to drive!) */
hri_port_set_PINCFG_DRVSTR_bit(PORT, 0, 11);
ccid_app_init();
}
/***********************************************************************
* CCID Driver integration
***********************************************************************/
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
#include "linuxlist_atomic.h"
#include "ccid_df.h"
struct usb_ep_q {
const char *name;
/* msgb queue of pending to-be-transmitted (IN/IRQ) or completed received (OUT)
* USB transfers */
struct llist_head list;
/* currently ongoing/processed msgb (USB transmit or receive */
struct msgb *in_progress;
};
struct ccid_state {
/* msgb queue of free msgs */
struct llist_head free_q;
/* msgb queue of pending to-be-transmitted (IN EP) */
struct usb_ep_q in_ep;
/* msgb queue of pending to-be-transmitted (IRQ EP) */
struct usb_ep_q irq_ep;
/* msgb queue of completed received (OUT EP) */
struct usb_ep_q out_ep;
/* bit-mask of card-insert status, as determined from NCN8025 IRQ output */
uint8_t card_insert_mask;
};
static volatile struct ccid_state g_ccid_s;
static void ccid_out_read_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred);
static void ccid_in_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred);
static void ccid_irq_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred);
static void usb_ep_q_init(struct usb_ep_q *ep_q, const char *name)
{
ep_q->name = name;
INIT_LLIST_HEAD(&ep_q->list);
ep_q->in_progress = NULL;
}
static void ccid_app_init(void)
{
/* initialize data structures */
INIT_LLIST_HEAD(&g_ccid_s.free_q);
usb_ep_q_init(&g_ccid_s.in_ep, "IN");
usb_ep_q_init(&g_ccid_s.irq_ep, "IRQ");
usb_ep_q_init(&g_ccid_s.out_ep, "OUT");
/* OUT endpoint read complete callback (irq context) */
ccid_df_register_callback(CCID_DF_CB_READ_OUT, (FUNC_PTR)&ccid_out_read_compl);
/* IN endpoint write complete callback (irq context) */
ccid_df_register_callback(CCID_DF_CB_WRITE_IN, (FUNC_PTR)&ccid_in_write_compl);
/* IRQ endpoint write complete callback (irq context) */
ccid_df_register_callback(CCID_DF_CB_WRITE_IRQ, (FUNC_PTR)&ccid_irq_write_compl);
}
/* irqsafe version of msgb_enqueue */
struct msgb *msgb_dequeue_irqsafe(struct llist_head *q)
{
struct msgb *msg;
CRITICAL_SECTION_ENTER()
msg = msgb_dequeue(q);
CRITICAL_SECTION_LEAVE()
return msg;
}
void msgb_enqueue_irqsafe(struct llist_head *q, struct msgb *msg)
{
CRITICAL_SECTION_ENTER()
msgb_enqueue(q, msg);
CRITICAL_SECTION_LEAVE()
}
/* submit the next pending (if any) message for the IN EP */
static int submit_next_in(void)
{
struct usb_ep_q *ep_q = &g_ccid_s.in_ep;
struct msgb *msg;
int rc;
if (ep_q->in_progress)
return 0;
msg = msgb_dequeue_irqsafe(&ep_q->list);
if (!msg)
return 0;
ep_q->in_progress = msg;
rc = ccid_df_write_in(msgb_data(msg), msgb_length(msg));
if (rc != ERR_NONE) {
printf("EP %s failed: %d\r\n", ep_q->name, rc);
return -1;
}
return 1;
}
/* submit the next pending (if any) message for the IRQ EP */
static int submit_next_irq(void)
{
struct usb_ep_q *ep_q = &g_ccid_s.irq_ep;
struct msgb *msg;
int rc;
if (ep_q->in_progress)
return 0;
msg = msgb_dequeue_irqsafe(&ep_q->list);
if (!msg)
return 0;
ep_q->in_progress = msg;
rc = ccid_df_write_irq(msgb_data(msg), msgb_length(msg));
/* may return HALTED/ERROR/DISABLED/BUSY/ERR_PARAM/ERR_FUNC/ERR_DENIED */
if (rc != ERR_NONE) {
printf("EP %s failed: %d\r\n", ep_q->name, rc);
return -1;
}
return 1;
}
static int submit_next_out(void)
{
struct usb_ep_q *ep_q = &g_ccid_s.out_ep;
struct msgb *msg;
int rc;
OSMO_ASSERT(!ep_q->in_progress);
msg = msgb_dequeue_irqsafe(&g_ccid_s.free_q);
if (!msg)
return -1;
msgb_reset(msg);
ep_q->in_progress = msg;
rc = ccid_df_read_out(msgb_data(msg), msgb_tailroom(msg));
if (rc != ERR_NONE) {
/* re-add to the list of free msgb's */
llist_add_tail_at(&g_ccid_s.free_q, &msg->list);
return 0;
}
return 1;
}
/* OUT endpoint read complete callback (irq context) */
static void ccid_out_read_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred)
{
struct msgb *msg = g_ccid_s.out_ep.in_progress;
/* add just-received msg to tail of endpoint queue */
OSMO_ASSERT(msg);
/* update msgb with the amount of data received */
msgb_put(msg, transferred);
/* append to list of pending-to-be-handed messages */
llist_add_tail_at(&msg->list, &g_ccid_s.out_ep.list);
g_ccid_s.out_ep.in_progress = NULL;
if(code != USB_XFER_DONE)
return;
/* submit another [free] msgb to receive the next transfer */
submit_next_out();
}
/* IN endpoint write complete callback (irq context) */
static void ccid_in_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred)
{
struct msgb *msg = g_ccid_s.in_ep.in_progress;
OSMO_ASSERT(msg);
/* return the message back to the queue of free message buffers */
llist_add_tail_at(&msg->list, &g_ccid_s.free_q);
g_ccid_s.in_ep.in_progress = NULL;
if(code != USB_XFER_DONE)
return;
/* submit the next pending to-be-transmitted msgb (if any) */
submit_next_in();
}
/* IRQ endpoint write complete callback (irq context) */
static void ccid_irq_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred)
{
struct msgb *msg = g_ccid_s.irq_ep.in_progress;
OSMO_ASSERT(msg);
/* return the message back to the queue of free message buffers */
llist_add_tail_at(&msg->list, &g_ccid_s.free_q);
g_ccid_s.irq_ep.in_progress = NULL;
if(code != USB_XFER_DONE)
return;
/* submit the next pending to-be-transmitted msgb (if any) */
submit_next_irq();
}
#include "ccid_proto.h"
static struct msgb *ccid_gen_notify_slot_status(uint8_t old_bm, uint8_t new_bm)
{
uint8_t statusbytes[2] = {0};
//struct msgb *msg = ccid_msgb_alloc();
struct msgb *msg = msgb_alloc(300,"IRQ");
struct ccid_rdr_to_pc_notify_slot_change *nsc = msgb_put(msg, sizeof(*nsc) + sizeof(statusbytes));
nsc->bMessageType = RDR_to_PC_NotifySlotChange;
for(int i = 0; i <8; i++) {
uint8_t byteidx = i >> 2;
uint8_t old_bit = (old_bm >> i) & 1;
uint8_t new_bit = (new_bm >> i) & 1;
uint8_t bv;
if (old_bit == new_bit && new_bit == 0)
bv = 0x00;
else if (old_bit == new_bit && new_bit == 1)
bv = 0x01;
else if (old_bit != new_bit && new_bit == 0)
bv = 0x02;
else
bv = 0x03;
statusbytes[byteidx] |= bv << ((i % 4) << 1);
}
memcpy(&nsc->bmSlotCCState, statusbytes, sizeof(statusbytes));
return msg;
}
/* check if any card detect state has changed */
static void poll_card_detect(void)
{
uint8_t new_mask = 0;
struct msgb *msg;
unsigned int i;
for (i = 0; i < 8; i++){
bool level = ncn8025_interrupt_level(i);
new_mask |= level << i;
g_ci.slot_ops->icc_set_insertion_status(&g_ci.slot[i], level);
}
/* notify the user/host about any changes */
if (g_ccid_s.card_insert_mask != new_mask) {
printf("CARD_DET 0x%02x -> 0x%02x\r\n",
g_ccid_s.card_insert_mask, new_mask);
msg = ccid_gen_notify_slot_status(g_ccid_s.card_insert_mask, new_mask);
msgb_enqueue_irqsafe(&g_ccid_s.irq_ep.list, msg);
g_ccid_s.card_insert_mask = new_mask;
}
}
/***********************************************************************
* Command Line interface
***********************************************************************/
extern void testmode_init(void);
extern void libosmo_emb_init(void);
extern void libosmo_emb_mainloop(void);
#include "talloc.h"
#include "logging.h"
void *g_tall_ctx;
/* Section 9.6 of SAMD5x/E5x Family Data Sheet */
static int get_chip_unique_serial(uint8_t *out, size_t len)
{
uint32_t *out32 = (uint32_t *)out;
if (len < 16)
return -EINVAL;
out32[0] = *(uint32_t *)0x008061fc;
out32[1] = *(uint32_t *)0x00806010;
out32[2] = *(uint32_t *)0x00806014;
out32[3] = *(uint32_t *)0x00806018;
return 0;
}
/* same as get_chip_unique_serial but in hex-string format */
static int get_chip_unique_serial_str(char *out, size_t len)
{
uint8_t buf[16];
int rc;
if (len < 16*2 + 1)
return -EINVAL;
rc = get_chip_unique_serial(buf, sizeof(buf));
if (rc < 0)
return rc;
osmo_hexdump_buf(out, len, buf, sizeof(buf), NULL, false);
return 0;
}
static int str_to_usb_desc(char* in, uint8_t in_sz, uint8_t* out, uint8_t out_sz){
if (2+in_sz*2 < out_sz)
return -1;
memset(out, 0, out_sz);
out[0] = out_sz;
out[1] = 0x3;
for (int i= 2; i < out_sz; i+=2)
out[i] = in[(i >> 1) - 1];
return 0;
}
#define RSTCAUSE_STR_SIZE 64
static void get_rstcause_str(char *out)
{
uint8_t val = hri_rstc_read_RCAUSE_reg(RSTC);
*out = '\0';
if (val & RSTC_RCAUSE_POR)
strcat(out, "POR ");
if (val & RSTC_RCAUSE_BODCORE)
strcat(out, "BODCORE ");
if (val & RSTC_RCAUSE_BODVDD)
strcat(out, "BODVDD ");
if (val & RSTC_RCAUSE_NVM)
strcat(out, "NVM ");
if (val & RSTC_RCAUSE_EXT)
strcat(out, "EXT ");
if (val & RSTC_RCAUSE_WDT)
strcat(out, "WDT ");
if (val & RSTC_RCAUSE_SYST)
strcat(out, "SYST ");
if (val & RSTC_RCAUSE_BACKUP)
strcat(out, "BACKUP ");
}
//#######################
static uint32_t clock_freqs[] = {
2500000
};
static uint32_t data_rates[] = {
6720
};
extern struct usb_desc_collection usb_fs_descs;
static int feed_ccid(void)
{
struct usb_ep_q *ep_q = &g_ccid_s.out_ep;
struct msgb *msg;
int rc;
msg = msgb_dequeue_irqsafe(&g_ccid_s.out_ep.list);
if (!msg)
return -1;
ccid_handle_out(&g_ci, msg);
return 1;
}
static int ccid_ops_send_in(struct ccid_instance *ci, struct msgb *msg)
{
/* add just-received msg to tail of endpoint queue */
OSMO_ASSERT(msg);
/* append to list of pending-to-be-handed messages */
llist_add_tail_at(&msg->list, &g_ccid_s.in_ep.list);
submit_next_in();
return 0;
}
static const struct ccid_ops c_ops = {
.send_in = ccid_ops_send_in,
.send_int = 0,
};
//#######################
#define NUM_OUT_BUF 16
char sernr_buf[16*2+1];
char product_buf[] = "sysmoOCTSIM "GIT_VERSION;
//len, type, 2 byte per hex char * 2 for unicode
uint8_t sernr_buf_descr[1+1+16*2*2];
uint8_t product_buf_descr[1+1+sizeof(product_buf)*2];
char rstcause_buf[RSTCAUSE_STR_SIZE];
int main(void)
{
#if 0
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk ; //| /* tracing*/
////CoreDebug_DEMCR_MON_EN_Msk; /* mon interupt catcher */
DWT->COMP0 = 0x40003028; /* sercom 0 data */
//DWT->COMP0 = 0x40003428; /* sercom 1 data */
DWT->MASK0 = 0; /* 0 */
DWT->FUNCTION0 = 0; /* has to be 0 for linked address */
DWT->COMP1 = 0xa5; /* value */
DWT->MASK1 = 0; /* match all bits */
DWT->FUNCTION1 = (0b10 << DWT_FUNCTION_DATAVSIZE_Pos) | /* DATAVSIZE 10 - dword */
#if 1
(0 << DWT_FUNCTION_DATAVADDR0_Pos) | /* Data Match linked addr pointer in COMP0 */
#else
(1 << DWT_FUNCTION_DATAVADDR0_Pos) | /* Data Match on any addr -> own number */
#endif
(1 << DWT_FUNCTION_DATAVMATCH_Pos) | /* DATAVMATCH Enable data comparation */
(0b0111 << DWT_FUNCTION_FUNCTION_Pos); /* generate a watchpoint event on rw */
#endif
atmel_start_init();
get_chip_unique_serial_str(sernr_buf, sizeof(sernr_buf));
str_to_usb_desc(sernr_buf, sizeof(sernr_buf), sernr_buf_descr, sizeof(sernr_buf_descr));
str_to_usb_desc(product_buf, sizeof(product_buf), product_buf_descr, sizeof(product_buf_descr));
get_rstcause_str(rstcause_buf);
usb_start();
board_init();
#ifdef WITH_DEBUG_CDC
command_init("sysmoOCTSIM> ");
#endif
/* boost uart priority by setting all other irqs to uartprio+1 */
for(int i = 0; i < PERIPH_COUNT_IRQn; i++)
NVIC_SetPriority(i, 2);
for(int i = SERCOM0_0_IRQn; i <= SERCOM7_3_IRQn; i++)
NVIC_SetPriority(i, 1);
printf("\r\n\r\n"
"=============================================================================\n\r"
"sysmoOCTSIM firmware " GIT_VERSION "\n\r"
"(C) 2018-2019 by sysmocom - s.f.m.c. GmbH and contributors\n\r"
"=============================================================================\n\r");
printf("Chip ID: %s\r\n", sernr_buf);
printf("Reset cause: %s\r\n", rstcause_buf);
talloc_enable_null_tracking();
g_tall_ctx = talloc_named_const(NULL, 0, "global");
printf("g_tall_ctx=%p\r\n", g_tall_ctx);
//FIXME osmo_emb has a pool?
msgb_talloc_ctx_init(g_tall_ctx, 0);
libosmo_emb_init();
LOGP(DUSB, LOGL_ERROR, "foobar usb\n");
//prevent spurious interrupts before our driver structs are ready
CRITICAL_SECTION_ENTER()
ccid_instance_init(&g_ci, &c_ops, &iso_fsm_slot_ops, &usb_fs_descs.ccid.class,
data_rates, clock_freqs, "", 0);
for(int i =0; i < NUM_OUT_BUF; i++){
struct msgb *msg = msgb_alloc(300, "ccid");
OSMO_ASSERT(msg);
/* return the message back to the queue of free message buffers */
llist_add_tail_at(&msg->list, &g_ccid_s.free_q);
}
submit_next_out();
CRITICAL_SECTION_LEAVE()
#if 0
/* CAN_RX */
gpio_set_pin_function(PIN_PB12, GPIO_PIN_FUNCTION_OFF);
gpio_set_pin_direction(PIN_PB12, GPIO_DIRECTION_OUT);
gpio_set_pin_level(PIN_PB12, false);
/* CAN_TX */
gpio_set_pin_function(PIN_PB13, GPIO_PIN_FUNCTION_OFF);
gpio_set_pin_direction(PIN_PB13, GPIO_DIRECTION_OUT);
gpio_set_pin_level(PIN_PB13, false);
#endif
// command_print_prompt();
while (true) { // main loop
command_try_recv();
poll_card_detect();
submit_next_irq();
for (int i = 0; i <= usb_fs_descs.ccid.class.bMaxSlotIndex; i++){
g_ci.slot_ops->handle_fsm_events(&g_ci.slot[i], true);
}
feed_ccid();
osmo_timers_update();
int qs = llist_count_at(&g_ccid_s.free_q);
if(qs > NUM_OUT_BUF)
for (int i= 0; i < qs-NUM_OUT_BUF; i++){
struct msgb *msg = msgb_dequeue_irqsafe(&g_ccid_s.free_q);
if (msg)
msgb_free(msg);
}
if(qs < NUM_OUT_BUF)
for (int i= 0; i < NUM_OUT_BUF-qs; i++){
struct msgb *msg = msgb_alloc(300,"ccid");
OSMO_ASSERT(msg);
/* return the message back to the queue of free message buffers */
llist_add_tail_at(&msg->list, &g_ccid_s.free_q);
}
}
}