mirror of
https://github.com/genesi/linux-legacy.git
synced 2026-09-06 14:57:47 +00:00
This is i.MX BSP 5.0.0 release ported to 2.6.31 Signed-off-by: Rob Herring <r.herring@freescale.com> Signed-off-by: Alan Tull <r80115@freescale.com> Signed-off-by: Xinyu Chen <xinyu.chen@freescale.com>
1124 lines
23 KiB
C
1124 lines
23 KiB
C
/*
|
|
* Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* The code contained herein is licensed under the GNU General Public
|
|
* License. You may obtain a copy of the GNU General Public License
|
|
* Version 2 or later at the following locations:
|
|
*
|
|
* http://www.opensource.org/licenses/gpl-license.html
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
|
|
|
|
#undef DI_DEBUG /* enable debug messages */
|
|
#undef DI_DEBUG_REGIO /* show register read/write */
|
|
#undef DI_TESTING /* include test code */
|
|
|
|
#ifdef DI_DEBUG
|
|
#define di_debug(fmt, arg...) os_printk(KERN_INFO fmt, ##arg)
|
|
#else
|
|
#define di_debug(fmt, arg...) do {} while (0)
|
|
#endif
|
|
|
|
#define di_info(fmt, arg...) os_printk(KERN_INFO fmt, ##arg)
|
|
#define di_warn(fmt, arg...) os_printk(KERN_WARNING fmt, ##arg)
|
|
|
|
#include "sahara2/include/portable_os.h"
|
|
#include "dryice.h"
|
|
#include "dryice-regs.h"
|
|
|
|
/* mask of the lock-related function flags */
|
|
#define DI_FUNC_LOCK_FLAGS (DI_FUNC_FLAG_READ_LOCK | \
|
|
DI_FUNC_FLAG_WRITE_LOCK | \
|
|
DI_FUNC_FLAG_HARD_LOCK)
|
|
|
|
/*
|
|
* dryice hardware states
|
|
*/
|
|
enum di_states {
|
|
DI_STATE_VALID = 0,
|
|
DI_STATE_NON_VALID,
|
|
DI_STATE_FAILURE,
|
|
};
|
|
|
|
/*
|
|
* todo list actions
|
|
*/
|
|
enum todo_actions {
|
|
TODO_ACT_WRITE_VAL,
|
|
TODO_ACT_WRITE_PTR,
|
|
TODO_ACT_WRITE_PTR32,
|
|
TODO_ACT_ASSIGN,
|
|
TODO_ACT_WAIT_RKG,
|
|
};
|
|
|
|
/*
|
|
* todo list status
|
|
*/
|
|
enum todo_status {
|
|
TODO_ST_LOADING,
|
|
TODO_ST_READY,
|
|
TODO_ST_PEND_WCF,
|
|
TODO_ST_PEND_RKG,
|
|
TODO_ST_DONE,
|
|
};
|
|
|
|
OS_DEV_INIT_DCL(dryice_init)
|
|
OS_DEV_SHUTDOWN_DCL(dryice_exit)
|
|
OS_DEV_ISR_DCL(dryice_norm_irq)
|
|
OS_WAIT_OBJECT(done_queue);
|
|
OS_WAIT_OBJECT(exit_queue);
|
|
|
|
struct dryice_data {
|
|
int busy; /* enforce exclusive access */
|
|
os_lock_t busy_lock;
|
|
int exit_flag; /* don't start new operations */
|
|
|
|
uint32_t baseaddr; /* physical base address */
|
|
void *ioaddr; /* virtual base address */
|
|
|
|
/* interrupt handling */
|
|
struct irq_struct {
|
|
os_interrupt_id_t irq;
|
|
int set;
|
|
} irq_norm, irq_sec;
|
|
|
|
struct clk *clk; /* clock control */
|
|
|
|
int key_programmed; /* key has been programmed */
|
|
int key_selected; /* key has been selected */
|
|
|
|
/* callback function and cookie */
|
|
void (*cb_func)(di_return_t rc, unsigned long cookie);
|
|
unsigned long cb_cookie;
|
|
} *di = NULL;
|
|
|
|
#define TODO_LIST_LEN 12
|
|
static struct {
|
|
struct td {
|
|
enum todo_actions action;
|
|
uint32_t src;
|
|
uint32_t dst;
|
|
int num;
|
|
} list[TODO_LIST_LEN];
|
|
int cur; /* current todo pointer */
|
|
int num; /* number of todo's on the list */
|
|
int async; /* non-zero if list is async */
|
|
int status; /* current status of the list */
|
|
di_return_t rc; /* return code generated by the list */
|
|
} todo;
|
|
|
|
/*
|
|
* dryice register read/write functions
|
|
*/
|
|
#ifdef DI_DEBUG_REGIO
|
|
static uint32_t di_read(int reg)
|
|
{
|
|
uint32_t val = os_read32(di->ioaddr + (reg));
|
|
di_info("di_read(0x%02x) = 0x%08x\n", reg, val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void di_write(uint32_t val, int reg)
|
|
{
|
|
di_info("dryice_write_reg(0x%08x, 0x%02x)\n", val, reg);
|
|
os_write32(di->ioaddr + (reg), val);
|
|
}
|
|
#else
|
|
#define di_read(reg) os_read32(di->ioaddr + (reg))
|
|
#define di_write(val, reg) os_write32(di->ioaddr + (reg), val);
|
|
#endif
|
|
|
|
/*
|
|
* set the dryice busy flag atomically, allowing
|
|
* for case where the driver is trying to exit.
|
|
*/
|
|
static int di_busy_set(void)
|
|
{
|
|
os_lock_context_t context;
|
|
int rc = 0;
|
|
|
|
os_lock_save_context(di->busy_lock, context);
|
|
if (di->exit_flag || di->busy)
|
|
rc = 1;
|
|
else
|
|
di->busy = 1;
|
|
os_unlock_restore_context(di->busy_lock, context);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* clear the dryice busy flag
|
|
*/
|
|
static inline void di_busy_clear(void)
|
|
{
|
|
/* don't acquire the lock because the race is benign */
|
|
di->busy = 0;
|
|
|
|
if (di->exit_flag)
|
|
os_wake_sleepers(exit_queue);
|
|
}
|
|
|
|
/*
|
|
* return the current state of dryice
|
|
* (valid, non-valid, or failure)
|
|
*/
|
|
static enum di_states di_state(void)
|
|
{
|
|
enum di_states state = DI_STATE_VALID;
|
|
uint32_t dsr = di_read(DSR);
|
|
|
|
if (dsr & DSR_NVF)
|
|
state = DI_STATE_NON_VALID;
|
|
else if (dsr & DSR_SVF)
|
|
state = DI_STATE_FAILURE;
|
|
|
|
return state;
|
|
}
|
|
|
|
#define DI_WRITE_LOOP_CNT 0x1000
|
|
/*
|
|
* the write-error flag is something that shouldn't get set
|
|
* during normal operation. if it's set something is terribly
|
|
* wrong. the best we can do is try to clear the bit and hope
|
|
* that dryice will recover. this situation is similar to an
|
|
* unexpected bus fault in terms of severity.
|
|
*/
|
|
static void try_to_clear_wef(void)
|
|
{
|
|
int cnt;
|
|
|
|
while (1) {
|
|
di_write(DSR_WEF, DSR);
|
|
for (cnt = 0; cnt < DI_WRITE_LOOP_CNT; cnt++) {
|
|
if ((di_read(DSR) & DSR_WEF) == 0)
|
|
break;
|
|
}
|
|
di_warn("WARNING: DryIce cannot clear DSR_WEF "
|
|
"(Write Error Flag)!\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* write a dryice register and loop, waiting for it
|
|
* to complete. use only during driver initialization.
|
|
* returns 0 on success or 1 on write failure.
|
|
*/
|
|
static int di_write_loop(uint32_t val, int reg)
|
|
{
|
|
int rc = 0;
|
|
int cnt;
|
|
|
|
di_debug("FUNC: %s\n", __func__);
|
|
di_write(val, reg);
|
|
|
|
for (cnt = 0; cnt < DI_WRITE_LOOP_CNT; cnt++) {
|
|
uint32_t dsr = di_read(DSR);
|
|
if (dsr & DSR_WEF) {
|
|
try_to_clear_wef();
|
|
rc = 1;
|
|
}
|
|
if (dsr & DSR_WCF)
|
|
break;
|
|
}
|
|
di_debug("wait_write_loop looped %d times\n", cnt);
|
|
if (cnt == DI_WRITE_LOOP_CNT)
|
|
rc = 1;
|
|
|
|
if (rc)
|
|
di_warn("DryIce wait_write_done: WRITE ERROR!\n");
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* initialize the todo list. must be called
|
|
* before adding items to the list.
|
|
*/
|
|
static void todo_init(int async_flag)
|
|
{
|
|
di_debug("FUNC: %s\n", __func__);
|
|
todo.cur = 0;
|
|
todo.num = 0;
|
|
todo.async = async_flag;
|
|
todo.rc = 0;
|
|
todo.status = TODO_ST_LOADING;
|
|
}
|
|
|
|
/*
|
|
* perform the current action on the todo list
|
|
*/
|
|
#define TC todo.list[todo.cur]
|
|
void todo_cur(void)
|
|
{
|
|
di_debug("FUNC: %s[%d]\n", __func__, todo.cur);
|
|
switch (TC.action) {
|
|
case TODO_ACT_WRITE_VAL:
|
|
di_debug(" TODO_ACT_WRITE_VAL\n");
|
|
/* enable the write-completion interrupt */
|
|
todo.status = TODO_ST_PEND_WCF;
|
|
di_write(di_read(DIER) | DIER_WCIE, DIER);
|
|
|
|
di_write(TC.src, TC.dst);
|
|
break;
|
|
|
|
case TODO_ACT_WRITE_PTR32:
|
|
di_debug(" TODO_ACT_WRITE_PTR32\n");
|
|
/* enable the write-completion interrupt */
|
|
todo.status = TODO_ST_PEND_WCF;
|
|
di_write(di_read(DIER) | DIER_WCIE, DIER);
|
|
|
|
di_write(*(uint32_t *)TC.src, TC.dst);
|
|
break;
|
|
|
|
case TODO_ACT_WRITE_PTR:
|
|
{
|
|
uint8_t *p = (uint8_t *)TC.src;
|
|
uint32_t val = 0;
|
|
int num = TC.num;
|
|
|
|
di_debug(" TODO_ACT_WRITE_PTR\n");
|
|
while (num--)
|
|
val = (val << 8) | *p++;
|
|
|
|
/* enable the write-completion interrupt */
|
|
todo.status = TODO_ST_PEND_WCF;
|
|
di_write(di_read(DIER) | DIER_WCIE, DIER);
|
|
|
|
di_write(val, TC.dst);
|
|
}
|
|
break;
|
|
|
|
case TODO_ACT_ASSIGN:
|
|
di_debug(" TODO_ACT_ASSIGN\n");
|
|
switch (TC.num) {
|
|
case 1:
|
|
*(uint8_t *)TC.dst = TC.src;
|
|
break;
|
|
case 2:
|
|
*(uint16_t *)TC.dst = TC.src;
|
|
break;
|
|
case 4:
|
|
*(uint32_t *)TC.dst = TC.src;
|
|
break;
|
|
default:
|
|
di_warn("Unexpected size in TODO_ACT_ASSIGN\n");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case TODO_ACT_WAIT_RKG:
|
|
di_debug(" TODO_ACT_WAIT_RKG\n");
|
|
/* enable the random-key interrupt */
|
|
todo.status = TODO_ST_PEND_RKG;
|
|
di_write(di_read(DIER) | DIER_RKIE, DIER);
|
|
break;
|
|
|
|
default:
|
|
di_debug(" TODO_ACT_NOOP\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* called when done with the todo list.
|
|
* if async, it does the callback.
|
|
* if blocking, it wakes up the caller.
|
|
*/
|
|
static void todo_done(di_return_t rc)
|
|
{
|
|
todo.rc = rc;
|
|
todo.status = TODO_ST_DONE;
|
|
if (todo.async) {
|
|
di_busy_clear();
|
|
if (di->cb_func)
|
|
di->cb_func(rc, di->cb_cookie);
|
|
} else
|
|
os_wake_sleepers(done_queue);
|
|
}
|
|
|
|
/*
|
|
* performs the actions sequentially from the todo list
|
|
* until it encounters an item that isn't ready.
|
|
*/
|
|
static void todo_run(void)
|
|
{
|
|
di_debug("FUNC: %s\n", __func__);
|
|
while (todo.status == TODO_ST_READY) {
|
|
if (todo.cur == todo.num) {
|
|
todo_done(0);
|
|
break;
|
|
}
|
|
todo_cur();
|
|
if (todo.status != TODO_ST_READY)
|
|
break;
|
|
todo.cur++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* kick off the todo list by making it ready
|
|
*/
|
|
static void todo_start(void)
|
|
{
|
|
di_debug("FUNC: %s\n", __func__);
|
|
todo.status = TODO_ST_READY;
|
|
todo_run();
|
|
}
|
|
|
|
/*
|
|
* blocking callers sleep here until the todo list is done
|
|
*/
|
|
static int todo_wait_done(void)
|
|
{
|
|
di_debug("FUNC: %s\n", __func__);
|
|
os_sleep(done_queue, todo.status == TODO_ST_DONE, 0);
|
|
|
|
return todo.rc;
|
|
}
|
|
|
|
/*
|
|
* add a dryice register write to the todo list.
|
|
* the value to be written is supplied.
|
|
*/
|
|
#define todo_write_val(val, reg) \
|
|
todo_add(TODO_ACT_WRITE_VAL, val, reg, 0)
|
|
|
|
/*
|
|
* add a dryice register write to the todo list.
|
|
* "size" bytes pointed to by addr will be written.
|
|
*/
|
|
#define todo_write_ptr(addr, reg, size) \
|
|
todo_add(TODO_ACT_WRITE_PTR, (uint32_t)addr, reg, size)
|
|
|
|
/*
|
|
* add a dryice register write to the todo list.
|
|
* the word pointed to by addr will be written.
|
|
*/
|
|
#define todo_write_ptr32(addr, reg) \
|
|
todo_add(TODO_ACT_WRITE_PTR32, (uint32_t)addr, reg, 0)
|
|
|
|
/*
|
|
* add a dryice memory write to the todo list.
|
|
* object can only have a size of 1, 2, or 4 bytes.
|
|
*/
|
|
#define todo_assign(var, val) \
|
|
todo_add(TODO_ACT_ASSIGN, val, (uint32_t)&(var), sizeof(var))
|
|
|
|
#define todo_wait_rkg() \
|
|
todo_add(TODO_ACT_WAIT_RKG, 0, 0, 0)
|
|
|
|
static void todo_add(int action, uint32_t src, uint32_t dst, int num)
|
|
{
|
|
struct td *p = &todo.list[todo.num];
|
|
|
|
di_debug("FUNC: %s\n", __func__);
|
|
if (todo.num == TODO_LIST_LEN) {
|
|
di_warn("WARNING: DryIce todo-list overflow!\n");
|
|
return;
|
|
}
|
|
p->action = action;
|
|
p->src = src;
|
|
p->dst = dst;
|
|
p->num = num;
|
|
todo.num++;
|
|
}
|
|
|
|
#if defined(DI_DEBUG) || defined(DI_TESTING)
|
|
/*
|
|
* print out the contents of the dryice status register
|
|
* with all the bits decoded
|
|
*/
|
|
static void show_dsr(const char *heading)
|
|
{
|
|
uint32_t dsr = di_read(DSR);
|
|
|
|
di_info("%s\n", heading);
|
|
if (dsr & DSR_TAMPER_BITS) {
|
|
if (dsr & DSR_WTD)
|
|
di_info("Wire-mesh Tampering Detected\n");
|
|
if (dsr & DSR_ETBD)
|
|
di_info("External Tampering B Detected\n");
|
|
if (dsr & DSR_ETAD)
|
|
di_info("External Tampering A Detected\n");
|
|
if (dsr & DSR_EBD)
|
|
di_info("External Boot Detected\n");
|
|
if (dsr & DSR_SAD)
|
|
di_info("Security Alarm Detected\n");
|
|
if (dsr & DSR_TTD)
|
|
di_info("Temperature Tampering Detected\n");
|
|
if (dsr & DSR_CTD)
|
|
di_info("Clock Tampering Detected\n");
|
|
if (dsr & DSR_VTD)
|
|
di_info("Voltage Tampering Detected\n");
|
|
if (dsr & DSR_MCO)
|
|
di_info("Monotonic Counter Overflow\n");
|
|
if (dsr & DSR_TCO)
|
|
di_info("Time Counter Overflow\n");
|
|
} else
|
|
di_info("No Tamper Events Detected\n");
|
|
|
|
di_info("%d Key Busy Flag\n", !!(dsr & DSR_KBF));
|
|
di_info("%d Write Busy Flag\n", !!(dsr & DSR_WBF));
|
|
di_info("%d Write Next Flag\n", !!(dsr & DSR_WNF));
|
|
di_info("%d Write Complete Flag\n", !!(dsr & DSR_WCF));
|
|
di_info("%d Write Error Flag\n", !!(dsr & DSR_WEF));
|
|
di_info("%d Random Key Error\n", !!(dsr & DSR_RKE));
|
|
di_info("%d Random Key Valid\n", !!(dsr & DSR_RKV));
|
|
di_info("%d Clock Alarm Flag\n", !!(dsr & DSR_CAF));
|
|
di_info("%d Non-Valid Flag\n", !!(dsr & DSR_NVF));
|
|
di_info("%d Security Violation Flag\n", !!(dsr & DSR_SVF));
|
|
}
|
|
|
|
/*
|
|
* print out a key in hex
|
|
*/
|
|
static void print_key(const char *tag, uint8_t *key, int bits)
|
|
{
|
|
int bytes = (bits + 7) / 8;
|
|
|
|
di_info("%s", tag);
|
|
while (bytes--)
|
|
os_printk("%02x", *key++);
|
|
os_printk("\n");
|
|
}
|
|
#endif /* defined(DI_DEBUG) || defined(DI_TESTING) */
|
|
|
|
/*
|
|
* dryice normal interrupt service routine
|
|
*/
|
|
OS_DEV_ISR(dryice_norm_irq)
|
|
{
|
|
/* save dryice status register */
|
|
uint32_t dsr = di_read(DSR);
|
|
|
|
if (dsr & DSR_WCF) {
|
|
/* disable the write-completion interrupt */
|
|
di_write(di_read(DIER) & ~DIER_WCIE, DIER);
|
|
|
|
if (todo.status == TODO_ST_PEND_WCF) {
|
|
if (dsr & DSR_WEF) {
|
|
try_to_clear_wef();
|
|
todo_done(DI_ERR_WRITE);
|
|
} else {
|
|
todo.cur++;
|
|
todo.status = TODO_ST_READY;
|
|
todo_run();
|
|
}
|
|
}
|
|
} else if (dsr & (DSR_RKV | DSR_RKE)) {
|
|
/* disable the random-key-gen interrupt */
|
|
di_write(di_read(DIER) & ~DIER_RKIE, DIER);
|
|
|
|
if (todo.status == TODO_ST_PEND_RKG) {
|
|
if (dsr & DSR_RKE)
|
|
todo_done(DI_ERR_FAIL);
|
|
else {
|
|
todo.cur++;
|
|
todo.status = TODO_ST_READY;
|
|
todo_run();
|
|
}
|
|
}
|
|
}
|
|
|
|
os_dev_isr_return(1);
|
|
}
|
|
|
|
/* write loop with error handling -- for init only */
|
|
#define di_write_loop_goto(val, reg, rc, label) \
|
|
do {if (di_write_loop(val, reg)) \
|
|
{rc = OS_ERROR_FAIL_S; goto label; } } while (0)
|
|
|
|
/*
|
|
* dryice driver initialization
|
|
*/
|
|
OS_DEV_INIT(dryice_init)
|
|
{
|
|
di_return_t rc = 0;
|
|
|
|
di_info("MXC DryIce driver\n");
|
|
|
|
/* allocate memory */
|
|
di = os_alloc_memory(sizeof(*di), GFP_KERNEL);
|
|
if (di == NULL) {
|
|
rc = OS_ERROR_NO_MEMORY_S;
|
|
goto err_alloc;
|
|
}
|
|
memset(di, 0, sizeof(*di));
|
|
di->baseaddr = DRYICE_BASE_ADDR;
|
|
di->irq_norm.irq = MXC_INT_DRYICE_NORM;
|
|
di->irq_sec.irq = MXC_INT_DRYICE_SEC;
|
|
|
|
/* map i/o registers */
|
|
di->ioaddr = os_map_device(di->baseaddr, DI_ADDRESS_RANGE);
|
|
if (di->ioaddr == NULL) {
|
|
rc = OS_ERROR_FAIL_S;
|
|
goto err_iomap;
|
|
}
|
|
|
|
/* allocate locks */
|
|
di->busy_lock = os_lock_alloc_init();
|
|
if (di->busy_lock == NULL) {
|
|
rc = OS_ERROR_NO_MEMORY_S;
|
|
goto err_locks;
|
|
}
|
|
|
|
/* enable clocks (is there a portable way to do this?) */
|
|
di->clk = clk_get(NULL, "dryice_clk");
|
|
clk_enable(di->clk);
|
|
|
|
/* register for interrupts */
|
|
/* os_register_interrupt() dosen't support an option to make the
|
|
interrupt as shared. Replaced it with request_irq().*/
|
|
rc = request_irq(di->irq_norm.irq, dryice_norm_irq, IRQF_SHARED,
|
|
"dry_ice", di);
|
|
if (rc)
|
|
goto err_irqs;
|
|
else
|
|
di->irq_norm.set = 1;
|
|
|
|
/*
|
|
* DRYICE HARDWARE INIT
|
|
*/
|
|
|
|
#ifdef DI_DEBUG
|
|
show_dsr("DSR Pre-Initialization State");
|
|
#endif
|
|
|
|
if (di_state() == DI_STATE_NON_VALID) {
|
|
uint32_t dsr = di_read(DSR);
|
|
|
|
di_debug("initializing from non-valid state\n");
|
|
|
|
/* clear security violation flag */
|
|
if (dsr & DSR_SVF)
|
|
di_write_loop_goto(DSR_SVF, DSR, rc, err_write);
|
|
|
|
/* clear tamper detect flags */
|
|
if (dsr & DSR_TAMPER_BITS)
|
|
di_write_loop_goto(DSR_TAMPER_BITS, DSR, rc, err_write);
|
|
|
|
/* initialize timers */
|
|
di_write_loop_goto(0, DTCLR, rc, err_write);
|
|
di_write_loop_goto(0, DTCMR, rc, err_write);
|
|
di_write_loop_goto(0, DMCR, rc, err_write);
|
|
|
|
/* clear non-valid flag */
|
|
di_write_loop_goto(DSR_NVF, DSR, rc, err_write);
|
|
}
|
|
|
|
/* set tamper events we are interested in watching */
|
|
di_write_loop_goto(DTCR_WTE | DTCR_ETBE | DTCR_ETAE, DTCR, rc,
|
|
err_write);
|
|
#ifdef DI_DEBUG
|
|
show_dsr("DSR Post-Initialization State");
|
|
#endif
|
|
os_dev_init_return(OS_ERROR_OK_S);
|
|
|
|
err_write:
|
|
/* unregister interrupts */
|
|
if (di->irq_norm.set)
|
|
os_deregister_interrupt(di->irq_norm.irq);
|
|
if (di->irq_sec.set)
|
|
os_deregister_interrupt(di->irq_sec.irq);
|
|
|
|
/* turn off clocks (is there a portable way to do this?) */
|
|
clk_disable(di->clk);
|
|
clk_put(di->clk);
|
|
|
|
err_irqs:
|
|
/* unallocate locks */
|
|
os_lock_deallocate(di->busy_lock);
|
|
|
|
err_locks:
|
|
/* unmap i/o registers */
|
|
os_unmap_device(di->ioaddr, DI_ADDRESS_RANGE);
|
|
|
|
err_iomap:
|
|
/* free the dryice struct */
|
|
os_free_memory(di);
|
|
|
|
err_alloc:
|
|
os_dev_init_return(rc);
|
|
}
|
|
|
|
/*
|
|
* dryice driver exit routine
|
|
*/
|
|
OS_DEV_SHUTDOWN(dryice_exit)
|
|
{
|
|
/* don't allow new operations */
|
|
di->exit_flag = 1;
|
|
|
|
/* wait for the current operation to complete */
|
|
os_sleep(exit_queue, di->busy == 0, 0);
|
|
|
|
/* unregister interrupts */
|
|
if (di->irq_norm.set)
|
|
os_deregister_interrupt(di->irq_norm.irq);
|
|
if (di->irq_sec.set)
|
|
os_deregister_interrupt(di->irq_sec.irq);
|
|
|
|
/* turn off clocks (is there a portable way to do this?) */
|
|
clk_disable(di->clk);
|
|
clk_put(di->clk);
|
|
|
|
/* unallocate locks */
|
|
os_lock_deallocate(di->busy_lock);
|
|
|
|
/* unmap i/o registers */
|
|
os_unmap_device(di->ioaddr, DI_ADDRESS_RANGE);
|
|
|
|
/* free the dryice struct */
|
|
os_free_memory(di);
|
|
|
|
os_dev_shutdown_return(OS_ERROR_OK_S);
|
|
}
|
|
|
|
di_return_t dryice_set_programmed_key(const void *key_data, int key_bits,
|
|
int flags)
|
|
{
|
|
uint32_t dcr;
|
|
int key_bytes, reg;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (key_data == NULL) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
if (key_bits < 0 || key_bits > MAX_KEY_LEN || key_bits % 8) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
if (flags & DI_FUNC_FLAG_WORD_KEY) {
|
|
if (key_bits % 32 || (uint32_t)key_data & 0x3) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
}
|
|
if (di->key_programmed) {
|
|
rc = DI_ERR_INUSE;
|
|
goto err;
|
|
}
|
|
if (di_state() == DI_STATE_FAILURE) {
|
|
rc = DI_ERR_STATE;
|
|
goto err;
|
|
}
|
|
dcr = di_read(DCR);
|
|
if (dcr & DCR_PKWHL) {
|
|
rc = DI_ERR_HLOCK;
|
|
goto err;
|
|
}
|
|
if (dcr & DCR_PKWSL) {
|
|
rc = DI_ERR_SLOCK;
|
|
goto err;
|
|
}
|
|
key_bytes = key_bits / 8;
|
|
|
|
todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0);
|
|
|
|
/* accomodate busses that can only do 32-bit transfers */
|
|
if (flags & DI_FUNC_FLAG_WORD_KEY) {
|
|
uint32_t *keyp = (void *)key_data;
|
|
|
|
for (reg = 0; reg < MAX_KEY_WORDS; reg++) {
|
|
if (reg < MAX_KEY_WORDS - key_bytes / 4)
|
|
todo_write_val(0, DPKR7 - reg * 4);
|
|
else {
|
|
todo_write_ptr32(keyp, DPKR7 - reg * 4);
|
|
keyp++;
|
|
}
|
|
}
|
|
} else {
|
|
uint8_t *keyp = (void *)key_data;
|
|
|
|
for (reg = 0; reg < MAX_KEY_WORDS; reg++) {
|
|
int size = key_bytes - (MAX_KEY_WORDS - reg - 1) * 4;
|
|
if (size <= 0)
|
|
todo_write_val(0, DPKR7 - reg * 4);
|
|
else {
|
|
if (size > 4)
|
|
size = 4;
|
|
todo_write_ptr(keyp, DPKR7 - reg * 4, size);
|
|
keyp += size;
|
|
}
|
|
}
|
|
}
|
|
todo_assign(di->key_programmed, 1);
|
|
|
|
if (flags & DI_FUNC_LOCK_FLAGS) {
|
|
dcr = di_read(DCR);
|
|
if (flags & DI_FUNC_FLAG_READ_LOCK) {
|
|
if (flags & DI_FUNC_FLAG_HARD_LOCK)
|
|
dcr |= DCR_PKRHL;
|
|
else
|
|
dcr |= DCR_PKRSL;
|
|
}
|
|
if (flags & DI_FUNC_FLAG_WRITE_LOCK) {
|
|
if (flags & DI_FUNC_FLAG_HARD_LOCK)
|
|
dcr |= DCR_PKWHL;
|
|
else
|
|
dcr |= DCR_PKWSL;
|
|
}
|
|
todo_write_val(dcr, DCR);
|
|
}
|
|
todo_start();
|
|
|
|
if (flags & DI_FUNC_FLAG_ASYNC)
|
|
return 0;
|
|
|
|
rc = todo_wait_done();
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_set_programmed_key);
|
|
|
|
di_return_t dryice_get_programmed_key(uint8_t *key_data, int key_bits)
|
|
{
|
|
int reg, byte, key_bytes;
|
|
uint32_t dcr, dpkr;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (key_data == NULL) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
if (key_bits < 0 || key_bits > MAX_KEY_LEN || key_bits % 8) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
#if 0
|
|
if (!di->key_programmed) {
|
|
rc = DI_ERR_UNSET;
|
|
goto err;
|
|
}
|
|
#endif
|
|
if (di_state() == DI_STATE_FAILURE) {
|
|
rc = DI_ERR_STATE;
|
|
goto err;
|
|
}
|
|
dcr = di_read(DCR);
|
|
if (dcr & DCR_PKRHL) {
|
|
rc = DI_ERR_HLOCK;
|
|
goto err;
|
|
}
|
|
if (dcr & DCR_PKRSL) {
|
|
rc = DI_ERR_SLOCK;
|
|
goto err;
|
|
}
|
|
key_bytes = key_bits / 8;
|
|
|
|
/* read key */
|
|
for (reg = 0; reg < MAX_KEY_WORDS; reg++) {
|
|
if (reg < (MAX_KEY_BYTES - key_bytes) / 4)
|
|
continue;
|
|
dpkr = di_read(DPKR7 - reg * 4);
|
|
|
|
for (byte = 0; byte < 4; byte++) {
|
|
if (reg * 4 + byte >= MAX_KEY_BYTES - key_bytes) {
|
|
int shift = 24 - byte * 8;
|
|
*key_data++ = (dpkr >> shift) & 0xff;
|
|
}
|
|
}
|
|
dpkr = 0; /* cleared for security */
|
|
}
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_get_programmed_key);
|
|
|
|
di_return_t dryice_release_programmed_key(void)
|
|
{
|
|
uint32_t dcr;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (!di->key_programmed) {
|
|
rc = DI_ERR_UNSET;
|
|
goto err;
|
|
}
|
|
dcr = di_read(DCR);
|
|
if (dcr & DCR_PKWHL) {
|
|
rc = DI_ERR_HLOCK;
|
|
goto err;
|
|
}
|
|
if (dcr & DCR_PKWSL) {
|
|
rc = DI_ERR_SLOCK;
|
|
goto err;
|
|
}
|
|
di->key_programmed = 0;
|
|
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_release_programmed_key);
|
|
|
|
di_return_t dryice_set_random_key(int flags)
|
|
{
|
|
uint32_t dcr;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (di_state() == DI_STATE_FAILURE) {
|
|
rc = DI_ERR_STATE;
|
|
goto err;
|
|
}
|
|
dcr = di_read(DCR);
|
|
if (dcr & DCR_RKHL) {
|
|
rc = DI_ERR_HLOCK;
|
|
goto err;
|
|
}
|
|
if (dcr & DCR_RKSL) {
|
|
rc = DI_ERR_SLOCK;
|
|
goto err;
|
|
}
|
|
todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0);
|
|
|
|
/* clear Random Key Error bit, if set */
|
|
if (di_read(DSR) & DSR_RKE)
|
|
todo_write_val(DSR_RKE, DCR);
|
|
|
|
/* load random key */
|
|
todo_write_val(DKCR_LRK, DKCR);
|
|
|
|
/* wait for RKV (valid) or RKE (error) */
|
|
todo_wait_rkg();
|
|
|
|
if (flags & DI_FUNC_LOCK_FLAGS) {
|
|
dcr = di_read(DCR);
|
|
if (flags & DI_FUNC_FLAG_WRITE_LOCK) {
|
|
if (flags & DI_FUNC_FLAG_HARD_LOCK)
|
|
dcr |= DCR_RKHL;
|
|
else
|
|
dcr |= DCR_RKSL;
|
|
}
|
|
todo_write_val(dcr, DCR);
|
|
}
|
|
todo_start();
|
|
|
|
if (flags & DI_FUNC_FLAG_ASYNC)
|
|
return 0;
|
|
|
|
rc = todo_wait_done();
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_set_random_key);
|
|
|
|
di_return_t dryice_select_key(di_key_t key, int flags)
|
|
{
|
|
uint32_t dcr, dksr;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
switch (key) {
|
|
case DI_KEY_FK:
|
|
dksr = DKSR_IIM_KEY;
|
|
break;
|
|
case DI_KEY_PK:
|
|
dksr = DKSR_PROG_KEY;
|
|
break;
|
|
case DI_KEY_RK:
|
|
dksr = DKSR_RAND_KEY;
|
|
break;
|
|
case DI_KEY_FPK:
|
|
dksr = DKSR_PROG_XOR_IIM_KEY;
|
|
break;
|
|
case DI_KEY_FRK:
|
|
dksr = DKSR_RAND_XOR_IIM_KEY;
|
|
break;
|
|
default:
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
if (di->key_selected) {
|
|
rc = DI_ERR_INUSE;
|
|
goto err;
|
|
}
|
|
if (di_state() != DI_STATE_VALID) {
|
|
rc = DI_ERR_STATE;
|
|
goto err;
|
|
}
|
|
dcr = di_read(DCR);
|
|
if (dcr & DCR_KSHL) {
|
|
rc = DI_ERR_HLOCK;
|
|
goto err;
|
|
}
|
|
if (dcr & DCR_KSSL) {
|
|
rc = DI_ERR_SLOCK;
|
|
goto err;
|
|
}
|
|
todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0);
|
|
|
|
/* select key */
|
|
todo_write_val(dksr, DKSR);
|
|
|
|
todo_assign(di->key_selected, 1);
|
|
|
|
if (flags & DI_FUNC_LOCK_FLAGS) {
|
|
dcr = di_read(DCR);
|
|
if (flags & DI_FUNC_FLAG_WRITE_LOCK) {
|
|
if (flags & DI_FUNC_FLAG_HARD_LOCK)
|
|
dcr |= DCR_KSHL;
|
|
else
|
|
dcr |= DCR_KSSL;
|
|
}
|
|
todo_write_val(dcr, DCR);
|
|
}
|
|
todo_start();
|
|
|
|
if (flags & DI_FUNC_FLAG_ASYNC)
|
|
return 0;
|
|
|
|
rc = todo_wait_done();
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_select_key);
|
|
|
|
di_return_t dryice_check_key(di_key_t *key)
|
|
{
|
|
uint32_t dksr;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (key == NULL) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
|
|
dksr = di_read(DKSR);
|
|
|
|
if (di_state() != DI_STATE_VALID) {
|
|
dksr = DKSR_IIM_KEY;
|
|
rc = DI_ERR_STATE;
|
|
} else if (dksr == DI_KEY_RK || dksr == DI_KEY_FRK) {
|
|
if (!(di_read(DSR) & DSR_RKV)) {
|
|
dksr = DKSR_IIM_KEY;
|
|
rc = DI_ERR_UNSET;
|
|
}
|
|
}
|
|
switch (dksr) {
|
|
case DKSR_IIM_KEY:
|
|
*key = DI_KEY_FK;
|
|
break;
|
|
case DKSR_PROG_KEY:
|
|
*key = DI_KEY_PK;
|
|
break;
|
|
case DKSR_RAND_KEY:
|
|
*key = DI_KEY_RK;
|
|
break;
|
|
case DKSR_PROG_XOR_IIM_KEY:
|
|
*key = DI_KEY_FPK;
|
|
break;
|
|
case DKSR_RAND_XOR_IIM_KEY:
|
|
*key = DI_KEY_FRK;
|
|
break;
|
|
}
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_check_key);
|
|
|
|
di_return_t dryice_release_key_selection(void)
|
|
{
|
|
uint32_t dcr;
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (!di->key_selected) {
|
|
rc = DI_ERR_UNSET;
|
|
goto err;
|
|
}
|
|
dcr = di_read(DCR);
|
|
if (dcr & DCR_KSHL) {
|
|
rc = DI_ERR_HLOCK;
|
|
goto err;
|
|
}
|
|
if (dcr & DCR_KSSL) {
|
|
rc = DI_ERR_SLOCK;
|
|
goto err;
|
|
}
|
|
di->key_selected = 0;
|
|
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_release_key_selection);
|
|
|
|
di_return_t dryice_get_tamper_event(uint32_t *events, uint32_t *timestamp,
|
|
int flags)
|
|
{
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
if (di_state() == DI_STATE_VALID) {
|
|
rc = DI_ERR_STATE;
|
|
goto err;
|
|
}
|
|
if (events == NULL) {
|
|
rc = DI_ERR_INVAL;
|
|
goto err;
|
|
}
|
|
*events = di_read(DSR) & DSR_TAMPER_BITS;
|
|
if (timestamp) {
|
|
if (di_state() == DI_STATE_NON_VALID)
|
|
*timestamp = di_read(DTCMR);
|
|
else
|
|
*timestamp = 0;
|
|
}
|
|
err:
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_get_tamper_event);
|
|
|
|
di_return_t dryice_register_callback(void (*func)(di_return_t,
|
|
unsigned long cookie),
|
|
unsigned long cookie)
|
|
{
|
|
di_return_t rc = 0;
|
|
|
|
if (di_busy_set())
|
|
return DI_ERR_BUSY;
|
|
|
|
di->cb_func = func;
|
|
di->cb_cookie = cookie;
|
|
|
|
di_busy_clear();
|
|
return rc;
|
|
}
|
|
EXTERN_SYMBOL(dryice_register_callback);
|
|
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_DESCRIPTION("DryIce");
|
|
MODULE_LICENSE("GPL");
|