iBoot/drivers/apple/cdma/cdma.c

1105 lines
30 KiB
C

/*
* Copyright (C) 2008-2011 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include <debug.h>
#include <drivers/aes.h>
#include <drivers/dma.h>
#include <platform.h>
#include <platform/int.h>
#include <platform/soc/hwdmachannels.h>
#include <platform/soc/hwisr.h>
#include <platform/soc/hwclocks.h>
#include <sys/task.h>
#include <sys/menu.h>
#include <sys.h>
#include "cdma.h"
#define kCDMASuccess (0)
#define kCDMAErrorNegativeSegment (-1)
#define kCDMAErrorSegmentsTooLong (-2)
#define kCDMAErrorEarlyZeroSegment (-3)
#define kCDMAErrorInvalidWordSize (-4)
#define kCDMAErrorInvalidBurstSize (-5)
#define kCDMAErrorNotIdle (-6)
#define kCDMAErrorNegativeLength (-7)
#define kCDMAErrorBadFifoAddress (-8)
#define kCDMAErrorLargeSegment (-9)
/*
* Tunable: how many commands to allocate for a channel when it is set up.
*/
#ifndef CDMA_CHANNEL_CMDS
# define CDMA_CHANNEL_CMDS 32
#endif
typedef enum {
st_NONE,
st_REWIND,
st_CONT
} CHANNEL_INTERRUPT_STATE;
struct cdma_channel {
int c_status;
#define CDMA_STATUS_UNINIT 0
#define CDMA_STATUS_IDLE 1
#define CDMA_STATUS_BUSY 2
#define CDMA_STATUS_BUSY_UNINIT 3
bool c_is_read;
u_int32_t c_fifo_address;
u_int32_t c_request_id_bits;
u_int32_t c_ccache_c;
u_int32_t c_ccache_r;
u_int32_t c_ccache_w;
struct cdma_command *c_chain;
/* state for longer multi-segment operations */
u_int32_t c_index;
u_int32_t c_resid;
struct dma_segment *c_sgl;
/* state for re-configuring after a peripheral stops us prematurely */
u_int32_t c_segment_offset;
u_int32_t c_previous_offset;
u_int32_t c_previous_index;
u_int32_t c_previous_resid;
/* callback for async operations */
dma_completion_function c_callback;
void *c_callback_arg;
/* saved for debugging */
u_int32_t c_length;
/* channel crypto config */
struct dma_aes_config *c_aes;
u_int32_t c_filter;
u_int32_t c_chunk;
/* Debugging - remove later */
CHANNEL_INTERRUPT_STATE irqState;
};
static struct cdma_channel dma_channel_softc[DMA_CHANNEL_COUNT];
#define CDMA_SOFTC(_x) (&dma_channel_softc[(_x) - 1]) /* NB: channels are 1-based */
static void dma_init_channel(int dma_channel, bool want_interrupt);
static void dma_continue_async(u_int32_t dma_channel);
static u_int32_t dma_generate_segments(int dma_channel);
static u_int32_t dma_generate_aes_segments(int dma_channel);
static void dma_int_handler(void *arg);
static void cdma_dump_channel(int channel);
/* filter contexts in use - context 1 is owned by the AES driver and not represented here */
static u_int32_t cdma_fc_inuse = 0;
#if CDMA_VERSION > 1
/*
* The platform is required to provide a table of associations between channels,
* request IDs and FIFO addresses.
*/
struct cdma_channel_config {
u_int32_t channel_low;
u_int32_t channel_high;
bool aes_ok;
u_int32_t request_id;
u_int32_t fifo_address;
};
static struct cdma_channel_config cdma_channel_configs[] = DMA_CHANNEL_CONFIG;
#define CDMA_CHANNEL_CONFIG_COUNT (sizeof(cdma_channel_configs) / sizeof(struct cdma_channel_config))
#endif
void dma_init(void)
{
dprintf(DEBUG_CRITICAL, "cdma_init()\n");
clock_gate(CLK_CDMA, true);
#if CDMA_VERSION >= 4
rCDMA_CLOCK_ON(1) = ((1 << CDMA_CLOCK_AES_WRAP) | (1 << CDMA_CLOCK_AES_CORE));
#endif /* CDMA_VERSION >= 4 */
}
static void dma_init_channel(int dma_channel, bool want_interrupt)
{
struct cdma_channel *cp;
u_int32_t vector;
int i;
cp = CDMA_SOFTC(dma_channel);
/* is the channel already set up? */
if (unlikely(cp->c_status == CDMA_STATUS_BUSY_UNINIT)) {
dprintf(DEBUG_SPEW, "CDMA: setting up channel %d\n", dma_channel);
/* allocate command chain */
cp->c_chain = memalign(sizeof(struct cdma_command) * CDMA_CHANNEL_CMDS, 32);
/* initialise segment linkage */
for (i = 0; i < (CDMA_CHANNEL_CMDS - 1); i++)
cp->c_chain[i].nxt = mem_static_map_physical((uintptr_t)&cp->c_chain[i + 1]);
/* mark channel as busy */
cp->c_status = CDMA_STATUS_BUSY;
/* invalidate fifo:request ID cache */
cp->c_fifo_address = 0;
cp->c_request_id_bits = 0;
#if CDMA_VERSION < 3
cp->c_ccache_c = 0;
cp->c_ccache_r = 0;
cp->c_ccache_w = 0;
#elif CDMA_VERSION < 5
/* Force shared by requesting cached for reads and writes */
cp->c_ccache_c = CDMA_CACHE_CACHE;
cp->c_ccache_r = CDMA_CACHE_CACHE;
cp->c_ccache_w = CDMA_CACHE_CACHE;
#else
/* Force shared by request write allocate for reads */
/* and read allocate for writes */
cp->c_ccache_c = CDMA_CACHE_BUFFER | CDMA_CACHE_CACHE | CDMA_CACHE_WALLOC;
cp->c_ccache_r = CDMA_CACHE_BUFFER | CDMA_CACHE_CACHE | CDMA_CACHE_WALLOC;
cp->c_ccache_w = CDMA_CACHE_CACHE | CDMA_CACHE_RALLOC;
#endif
/* install interrupt handler */
/* NB: assumes channels allocated in linear order */
vector = INT_CDMA_DMAC1 + dma_channel - 1;
if (want_interrupt) {
set_int_type(vector, INT_TYPE_IRQ | INT_TYPE_LEVEL);
install_int_handler(vector, dma_int_handler, (void *)dma_channel);
unmask_int(vector);
} else {
mask_int(vector);
}
}
cp->irqState = st_NONE;
}
int
dma_execute_cmd(
u_int32_t cmd,
int dma_channel,
void *src,
void *dst,
u_int32_t length,
u_int32_t word_size,
u_int32_t burst_size)
{
struct task_event event;
struct dma_segment seg;
void *fifo;
int32_t ret;
/* build an SGL for the one segment */
switch (cmd & DMA_CMD_DIR_MASK) {
case DMA_CMD_DIR_TX:
seg.paddr = (u_int32_t)src;
fifo = dst;
break;
case DMA_CMD_DIR_RX:
seg.paddr = (u_int32_t)dst;
fifo = src;
break;
default:
case DMA_CMD_DIR_MEM : /* XXX not legitimate here, to be removed */
return EINVAL;
}
seg.length = length;
/* run the operation */
event_init(&event, EVENT_FLAG_AUTO_UNSIGNAL, false);
ret = dma_execute_async(
cmd,
dma_channel,
&seg,
fifo,
length,
word_size,
burst_size,
(dma_completion_function)event_signal,
(void *)&event);
if (!ret) {
/* and block waiting for it to complete */
event_wait(&event);
}
return ret;
}
int
dma_execute_async(
u_int32_t cmd,
int dma_channel,
struct dma_segment *sgl,
void *fifo,
u_int32_t length,
u_int32_t word_size,
u_int32_t burst_size,
dma_completion_function completion,
void *completion_arg)
{
struct dma_segment *cur_sgl;
struct cdma_channel *cp;
u_int32_t dw_fld, dbs_fld;
u_int32_t seg_length = 0;
ASSERT(sgl);
ASSERT((dma_channel > 2) && (dma_channel <= DMA_CHANNEL_COUNT));
ASSERT(length > 0);
ASSERT((length % word_size) == 0);
/*
* Verify that the total lenght of the transfer < 2GB(neg) because the DBR cannot be negative!
* Any number this big would be wrong anyways. We don't want to assert because there is
* calling logic that spits out some useful information upon error.
*/
if ((int32_t)length <= 0) {
dprintf(DEBUG_CRITICAL, "kCDMAErrorNegativeLength: %d", (int32_t)length);
return kCDMAErrorNegativeLength;
}
cp = CDMA_SOFTC(dma_channel);
enter_critical_section();
if (cp->c_status != CDMA_STATUS_IDLE && cp->c_status != CDMA_STATUS_UNINIT) {
exit_critical_section();
dprintf(DEBUG_CRITICAL, "kCDMAErrorNotIdle");
return kCDMAErrorNotIdle;
}
/* mark channel as busy */
cp->c_status = (cp->c_status == CDMA_STATUS_UNINIT) ?
CDMA_STATUS_BUSY_UNINIT : CDMA_STATUS_BUSY;
exit_critical_section();
/* do channel state init */
dma_init_channel(dma_channel, true);
/* turn on channel clock */
cdma_clock_enable(dma_channel, true);
#if CDMA_VERSION > 1
/*
* Check the channel request ID cache.
*
* For CDMA_VERSION == 1, these bits are always zero.
*/
if (unlikely((u_int32_t)fifo != cp->c_fifo_address)) {
u_int32_t i;
for (i = 0; i < CDMA_CHANNEL_CONFIG_COUNT; i++) {
if (cdma_channel_configs[i].fifo_address == (u_int32_t)fifo) {
/* sanity */
ASSERT((unsigned)dma_channel >= cdma_channel_configs[i].channel_low);
ASSERT((unsigned)dma_channel <= cdma_channel_configs[i].channel_high);
if (NULL != cp->c_aes) {
ASSERT(true == cdma_channel_configs[i].aes_ok);
}
/* cache the request ID */
cp->c_fifo_address = cdma_channel_configs[i].fifo_address;
cp->c_request_id_bits = CDMA_DCR_REQ(cdma_channel_configs[i].request_id);
/* and done */
break;
}
}
/* sanity */
ASSERT((u_int32_t)fifo == cp->c_fifo_address);
if (unlikely((u_int32_t)fifo != cp->c_fifo_address))
return kCDMAErrorBadFifoAddress;
}
#endif
/* initialise the channel sgl state */
cp->c_index = 0;
cp->c_sgl = sgl;
cp->c_resid = cp->c_length = length;
cp->c_segment_offset = 0;
/* Validate segments length match out length */
cur_sgl = sgl;
while(1) {
/* Check that the sgl->length is less than 2GB (neg).
* Also verify that a segment is no larger than 16mb.
* It is highly unlikely that this will ever occur.
*/
if ((int32_t)cur_sgl->length < 0 ) {
dprintf(DEBUG_CRITICAL, "kCDMAErrorNegativeSegment: 0x%08x", cur_sgl->length);
return kCDMAErrorNegativeSegment;
}
if (cur_sgl->length > (16 * 1024 * 1024)) {
dprintf(DEBUG_CRITICAL, "kCDMAErrorLargeSegment: 0x%08x", cur_sgl->length);
return kCDMAErrorLargeSegment;
}
seg_length += cur_sgl->length;
/*
* Check that we haven't gone over the length.
* Check that we dont have a zero length segment before we get to the end.
*/
if (seg_length > length) return kCDMAErrorSegmentsTooLong;
if (seg_length < length && cur_sgl->length == 0) return kCDMAErrorEarlyZeroSegment;
/* Finished the check */
if (seg_length == length) break;
cur_sgl++;
}
/* save the completion */
cp->c_callback = completion;
cp->c_callback_arg = completion_arg;
/* determine the command direction */
cp->c_is_read = (cmd == DMA_CMD_DIR_TX);
/* configure the channel */
switch (word_size) {
case 1 : dw_fld = CDMA_DCR_DW_1; break;
case 2 : dw_fld = CDMA_DCR_DW_2; break;
case 4 : dw_fld = CDMA_DCR_DW_4; break;
default :
return kCDMAErrorInvalidWordSize;
}
switch (burst_size) {
case 1 : dbs_fld = CDMA_DCR_DBS_1; break;
case 2 : dbs_fld = CDMA_DCR_DBS_2; break;
case 4 : dbs_fld = CDMA_DCR_DBS_4; break;
case 8 : dbs_fld = CDMA_DCR_DBS_8; break;
case 16 : dbs_fld = CDMA_DCR_DBS_16; break;
case 32 : dbs_fld = CDMA_DCR_DBS_32; break;
default :
return kCDMAErrorInvalidBurstSize;
}
/*
* Must always abort the channel before configuring it in order to reset the state machine.
* Note that moving from IDLE (which looks like HALTED) to HALTED, or even from HALTED to
* HALTED can generate a spurious HALT interrupt, so mask everything.
*/
rCDMA_CHANNEL_CSR(dma_channel) = CDMA_CSR_ABT;
/* configure channel config */
rCDMA_CHANNEL_DCR(dma_channel) =
(cp->c_is_read ? CDMA_DCR_DTD_TX : CDMA_DCR_DTD_RX) |
dw_fld |
dbs_fld |
cp->c_request_id_bits;
#if CDMA_CHANNEL_ARASAN_QUIRKS
if ((1 << dma_channel) & CDMA_CHANNEL_ARASAN_QUIRKS) {
/* for Arasan, TX direction must be streaming mode */
if (cp->c_is_read)
rCDMA_CHANNEL_DCR(dma_channel) |= CDMA_DCR_ST;
}
#endif
rCDMA_CHANNEL_DAR(dma_channel) = (u_int32_t)fifo;
rCDMA_CHANNEL_DBR(dma_channel) = length;
/* init for AES if required */
if (cp->c_aes) {
ASSERT(0 == (length % cp->c_aes->chunk_size));
cp->c_chunk = 0;
}
/* configure for initial data and start the channel */
dma_continue_async(dma_channel);
return kCDMASuccess;
}
void
dma_use_int(int dma_channel, bool use)
{
u_int32_t vector = INT_CDMA_DMAC1 + dma_channel - 1;
if (use)
{
unmask_int(vector);
}
else
{
mask_int(vector);
}
}
bool
dma_poll(int dma_channel)
{
bool ret = false;
struct cdma_channel *cp;
u_int32_t csr;
cp = CDMA_SOFTC(dma_channel);
if (CDMA_STATUS_IDLE == cp->c_status)
{
ret = true;
goto exit;
}
enter_critical_section();
csr = rCDMA_CHANNEL_CSR(dma_channel);
if (((0 != (CDMA_CSR_CIE & csr)) &&
(0 != (CDMA_CSR_CIR & csr))) ||
((0 != (CDMA_CSR_HIE & csr)) &&
(0 != (CDMA_CSR_HIR & csr))))
{
dma_int_handler((void *)dma_channel);
}
exit_critical_section();
if (CDMA_STATUS_IDLE == cp->c_status)
{
ret = true;
goto exit;
}
exit:
return ret;
}
static void
dma_continue_async(u_int32_t dma_channel)
{
struct cdma_channel *cp;
u_int32_t count;
cp = CDMA_SOFTC(dma_channel);
RELEASE_ASSERT(0 < cp->c_resid);
/* save state for restart */
cp->c_previous_index = cp->c_index;
cp->c_previous_resid = cp->c_resid;
cp->c_previous_offset = cp->c_segment_offset;
if (NULL == cp->c_aes) {
count = dma_generate_segments(dma_channel);
} else {
count = dma_generate_aes_segments(dma_channel);
}
/* we're done with messing with the command chain, make sure it's in memory */
ASSERT(count > 0);
platform_cache_operation(CACHE_CLEAN, cp->c_chain, sizeof(*cp->c_chain) * count);
/* clear and enable interrupts and (re)start the channel */
rCDMA_CHANNEL_CAR(dma_channel) = mem_static_map_physical((uintptr_t)cp->c_chain);
rCDMA_CHANNEL_CSR(dma_channel) =
CDMA_CSR_GO |
CDMA_CSR_HIE |
CDMA_CSR_ERR |
CDMA_CSR_HIR |
CDMA_CSR_CIR |
(cp->c_aes ? CDMA_CSR_FC(cp->c_filter) : 0) |
CDMA_CSR_CCACHE(cp->c_ccache_c);
}
static u_int32_t
dma_generate_segments(int dma_channel)
{
struct cdma_channel *cp;
struct cdma_command *cc;
struct dma_segment *sge;
int i;
u_int32_t segment_length;
cp = CDMA_SOFTC(dma_channel);
/* generate segment commands */
for (i = 0; i < (CDMA_CHANNEL_CMDS - 1); i++) {
/* get the next command */
cc = cp->c_chain + i;
/* get the next segment */
sge = cp->c_sgl + cp->c_index++;
/*
* Make a memory command for this segment.
*
* The segment offset may be nonzero for the first
* segment only, and is used to compensate for operations
* that are terminated presumptively by the peripheral in
* the middle of a segment.
*/
segment_length = sge->length - cp->c_segment_offset;
cc->ctrl = CDMA_CTRL_CMD_MEM | CDMA_CTRL_CACHE(cp->c_is_read ? cp->c_ccache_r : cp->c_ccache_w);
cc->addr = sge->paddr + cp->c_segment_offset;
cc->length = segment_length;
if ( 0==cc->length )
{
panic("Caught trying to generate zero-length cdma segment on channel %d, irqState: %d",dma_channel,(int)cp->irqState);
}
cp->c_segment_offset = 0;
if (segment_length < cp->c_resid) {
/* account for this segment */
cp->c_resid -= segment_length;
} else {
ASSERT(segment_length == cp->c_resid);
cp->c_resid = 0;
break;
}
}
/* barrier the last MEM command to ensure that data drains before we reach the HLT and interrupt */
cc->ctrl |= CDMA_CTRL_BAR;
/* always make the next command a HALT */
(cc + 1)->ctrl = CDMA_CTRL_CMD_HLT;
return(i + 2);
}
static u_int32_t
dma_generate_aes_segments(int dma_channel)
{
struct cdma_channel *cp;
struct cdma_command *cc;
struct dma_segment *sge;
int i;
u_int32_t segment_length;
u_int32_t chunk_done;
cp = CDMA_SOFTC(dma_channel);
i = 0;
cc = cp->c_chain;
sge = cp->c_sgl + cp->c_index;
/* loop emitting pairs of LDIV and chunk-sized transfers */
for (; cp->c_resid > 0; cp->c_resid -= cp->c_aes->chunk_size) {
/*
* Assuming a virtually contiguous input buffer, in the worst case
* we will emit three commands here, make sure there is room for them
* plus the terminating HLT.
*/
if ((i + 4) >= CDMA_CHANNEL_CMDS)
break;
/*
* We will always come here at the beginning of a chunk, so
* emit an LDIV and get the supplier to fill in the IV.
*/
cc->ctrl = CDMA_CTRL_CMD_LDIV;
cp->c_aes->iv_func(cp->c_aes->iv_func_arg, cp->c_chunk, &cc->iv0);
cc++;
i++;
cp->c_chunk++;
chunk_done = 0;
/*
* Emit part or all of a chunk. We may run out of bytes in this
* segment, which is OK (we'll make another one).
*/
while (chunk_done < cp->c_aes->chunk_size) {
ASSERT(i < (CDMA_CHANNEL_CMDS - 1));
/* find the space left in this sge */
segment_length = sge->length - cp->c_segment_offset;
/* limit the segment to completing one chunk */
if ((chunk_done + segment_length) > cp->c_aes->chunk_size)
segment_length = (cp->c_aes->chunk_size - chunk_done);
/* make the command */
cc->ctrl = CDMA_CTRL_CMD_MEM | CDMA_CTRL_CACHE(cp->c_is_read ? cp->c_ccache_r : cp->c_ccache_w) | CDMA_CTRL_FE | ((chunk_done == 0) ? CDMA_CTRL_FR : 0);
cc->addr = sge->paddr + cp->c_segment_offset;
cc->length = segment_length;
if ( 0==cc->length )
{
panic("Caught trying to generate zero-length cdma segment on channel %d, irqState: %d",dma_channel,(int)cp->irqState);
}
/* update running state */
chunk_done += segment_length;
cp->c_segment_offset += segment_length;
cc++;
i++;
/* did we just finish that sge? */
if (cp->c_segment_offset >= sge->length) {
sge++;
cp->c_index++;
cp->c_segment_offset = 0;
}
}
}
if (0 == cp->c_resid) {
/* we must have generated at least two segments... */
ASSERT(cc != cp->c_chain);
}
/* barrier the last MEM command to ensure that data drains before we reach the HLT and interrupt */
(cc - 1)->ctrl |= CDMA_CTRL_BAR;
/* make the next command a HALT */
cc->ctrl = CDMA_CTRL_CMD_HLT;
return(i + 2);
}
static void
dma_int_handler(void *arg)
{
u_int32_t dma_channel = (u_int32_t)arg;
struct cdma_channel *cp;
int i;
u_int32_t scan_resid;
u_int32_t csr;
struct dma_segment *sge;
cp = CDMA_SOFTC(dma_channel);
csr = rCDMA_CHANNEL_CSR(dma_channel);
ASSERT(CDMA_CSR_RUN(csr) != CDMA_CSR_RUN_RUNNING);
if (unlikely(csr & CDMA_CSR_ERR)) {
#if CDMA_VERSION > 1
panic("CDMA: channel %d error interrupt, error status 0x%0x", dma_channel,
rCDMA_CHANNEL_ERR(dma_channel) & rCDMA_CHANNEL_ERR_MASK);
#else
panic("CDMA: channel %d error interrupt", dma_channel);
#endif
}
if (unlikely(csr & CDMA_CSR_CIR))
panic("CDMA: channel %d spurious CIR, status 0x%0x", dma_channel, csr);
if (unlikely(!(csr & CDMA_CSR_HIR)))
panic("CDMA: channel %d spurious interrupt, status 0x%0x", dma_channel, csr);
/* clear interrupt status, mask further interrupts */
rCDMA_CHANNEL_CSR(dma_channel) = CDMA_CSR_HIR;
/*
* Is the channel done?
* Note that we maintain the DBR even if the channel is quirked for
* streaming mode so that this test is valid in either case.
*/
if (cp->c_resid == 0 && rCDMA_CHANNEL_DBR(dma_channel) <= 0) {
/* clear AES config */
dma_set_aes(dma_channel, NULL);
/* turn off the channel clock */
cdma_clock_enable(dma_channel, false);
/* mark as idle so that the channel can be re-used in the callback */
cp->c_status = CDMA_STATUS_IDLE;
/* call the user-supplied callback */
if (cp->c_callback != NULL)
cp->c_callback(cp->c_callback_arg);
} else if (CDMA_CSR_RUN_HALTED == CDMA_CSR_RUN(rCDMA_CHANNEL_CSR(dma_channel))) {
/* Check that hardware and software are in agreement about how much data is left to
* transfer. We never expect to encounter an interrupt where the hardware hasn't
* consumed the entire segment list we last provided. This check relies on a BAR to
* have drained the FIFO towards memory (but the DBR can get ahead on an RX by up
* to one FIFO length - hence the inequality). */
int32_t dbr = rCDMA_CHANNEL_DBR(dma_channel);
if (dbr > (int32_t)cp->c_resid) {
panic("Failed DBR/resid check: ch%d, c_resid=0x%08x DBR=0x%08x\n", dma_channel, cp->c_resid, dbr);
}
/* the channel has hit the halt terminating the current segment set */
cp->irqState = st_CONT;
/* re-configure & restart the channel */
dma_continue_async(dma_channel);
} else {
/* the channel has been stopped by the peripheral */
ASSERT(CDMA_CSR_RUN_STOPPED == CDMA_CSR_RUN(rCDMA_CHANNEL_CSR(dma_channel)));
/* fix up residual count and work out where we ended up in the current segment set */
/* XXX this should probably just be cp->c_resid = rCDMA_CHANNEL_DBR(dma_channel) */
cp->c_resid += rCDMA_CHANNEL_DBR(dma_channel);
scan_resid = cp->c_previous_resid - cp->c_resid;
/* walk up the segment list trying to work out where the peripheral stopped */
for (i = 0; i < CDMA_CHANNEL_CMDS; i++) {
/* get the next segment */
sge = cp->c_sgl + cp->c_previous_index + i;
/* did it stop in this segment? */
if ((sge->length - cp->c_previous_offset) >= scan_resid)
break;
/* account for the segment */
scan_resid -= (sge->length - cp->c_previous_offset);
cp->c_previous_offset = 0;
}
/* it should be impossible to not find the terminating case in the segments we loaded */
ASSERT(i < CDMA_CHANNEL_CMDS);
/* rewind the DMA state back to the segment in which it stopped */
if (((sge->length - cp->c_previous_offset) == scan_resid)) {
/* current segment is finished, so skip it b4 reconfigure the DMA */
cp->c_index = cp->c_previous_index + i + 1;
cp->c_segment_offset = 0;
} else {
/* re-configure the CDMA */
cp->c_index = cp->c_previous_index + i;
cp->c_segment_offset = cp->c_previous_offset + scan_resid;
}
cp->irqState = st_REWIND;
/* now we can re-configure the CDMA */
dma_continue_async(dma_channel);
}
}
void
dma_cancel(int dma_channel)
{
struct cdma_channel *cp;
utime_t start_time;
cp = CDMA_SOFTC(dma_channel);
ASSERT((dma_channel > 0) && (dma_channel <= DMA_CHANNEL_COUNT));
/* it's legal but not interesting to cancel a channel that hasn't been initialised */
if (CDMA_STATUS_UNINIT == cp->c_status)
return;
dprintf(DEBUG_INFO, "CDMA: cancelling channel\n");
/*
* Abort the channel.
*
* In the general case, we would also have to wait for the CDMA to either be
* processing a MEM command (in which case ABORT is effective immediately) or
* stopped for some other reason. Since we know that our command lists are always
* MEM or HLT, we can be sure this will be immediately effective.
*/
start_time = system_time();
cdma_clock_enable(dma_channel, true);
if (CDMA_CSR_RUN_RUNNING == CDMA_CSR_RUN(rCDMA_CHANNEL_CSR(dma_channel))) {
#if CDMA_VERSION < 2
/* version 1 CDMA requires pause before abort */
rCDMA_CHANNEL_CSR(dma_channel) = CDMA_CSR_PS;
while (!(rCDMA_CHANNEL_CSR(dma_channel) & CDMA_CSR_PSD)) {
if (time_has_elapsed(start_time, 10))
panic("CDMA: channel %d pause timeout during abort", dma_channel);
}
#endif
rCDMA_CHANNEL_CSR(dma_channel) = CDMA_CSR_ABT;
while (CDMA_CSR_RUN_RUNNING == CDMA_CSR_RUN(rCDMA_CHANNEL_CSR(dma_channel))) {
if (time_has_elapsed(start_time, 10000))
panic("CDMA: channel %d timeout during abort", dma_channel);
}
}
/* clear the crypto config */
dma_set_aes(dma_channel, NULL);
/* turn off the channel clock */
cdma_clock_enable(dma_channel, false);
/* mark the channel idle */
cp->c_status = CDMA_STATUS_IDLE;
}
/*
* Set the AES configuration for (dma_channel).
*
* If (config) is NULL, AES is disabled for the channel.
*/
int
dma_set_aes(int dma_channel, struct dma_aes_config *config)
{
struct cdma_channel *cp;
int i;
u_int32_t fcsr;
bool clock_state;
ASSERT((dma_channel > 0) && (dma_channel <= DMA_CHANNEL_COUNT));
cp = CDMA_SOFTC(dma_channel);
cp->c_aes = config;
if (NULL != config) {
/*
* For v2+ we must turn on at least one AES-using channel before
* the AES block will be clocked. Since we can't be sure whether
* one is on or not, turn this one on and remember whether it was
* already on so that we don't turn it off inadvertently.
*/
clock_state = cdma_clock_enable(dma_channel, true);
/* assign an AES filter context */
if (0 == cp->c_filter) {
enter_critical_section();
for (i = 2; i < CDMA_FILTER_CONTEXTS; i++) {
if (!(cdma_fc_inuse & (1 << i))) {
cp->c_filter = i;
cdma_fc_inuse |= (1 << i);
break;
}
}
exit_critical_section();
}
if (0 == cp->c_filter)
panic("CDMA: no AES filter contexts: 0x%08x", cdma_fc_inuse);
/* invariants */
fcsr = CDMA_FCSR_CHANNEL(dma_channel) | CDMA_FCSR_CBC;
/* direction */
if ((cp->c_aes->command & AES_CMD_DIR_MASK) == AES_CMD_ENC)
fcsr |= CDMA_FCSR_ENC;
/* key size */
switch (cp->c_aes->keying & AES_KEY_SIZE_MASK) {
case AES_KEY_SIZE_128:
fcsr |= CDMA_FCSR_KL_128;
break;
case AES_KEY_SIZE_192:
fcsr |= CDMA_FCSR_KL_192;
break;
case AES_KEY_SIZE_256:
fcsr |= CDMA_FCSR_KL_256;
break;
default:
ASSERT(false);
return(-1);
}
/* key selection/load */
switch (cp->c_aes->keying & AES_KEY_TYPE_MASK) {
case AES_KEY_TYPE_USER:
fcsr |= CDMA_FCSR_KS_VARIABLE;
ASSERT(NULL != cp->c_aes->key);
switch (cp->c_aes->keying & AES_KEY_SIZE_MASK) {
case AES_KEY_SIZE_256:
rCDMA_FILTER_KBR7(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[7];
rCDMA_FILTER_KBR6(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[6];
case AES_KEY_SIZE_192:
rCDMA_FILTER_KBR5(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[5];
rCDMA_FILTER_KBR4(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[4];
case AES_KEY_SIZE_128:
rCDMA_FILTER_KBR3(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[3];
rCDMA_FILTER_KBR2(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[2];
rCDMA_FILTER_KBR1(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[1];
rCDMA_FILTER_KBR0(cp->c_filter) = ((u_int32_t *)cp->c_aes->key)[0];
break;
default:
ASSERT(false);
return(-1);
}
break;
case AES_KEY_TYPE_UID0:
fcsr |= CDMA_FCSR_KEY(0);
break;
case AES_KEY_TYPE_GID0:
fcsr |= CDMA_FCSR_KEY(1);
break;
case AES_KEY_TYPE_GID1:
fcsr |= CDMA_FCSR_KEY(2);
break;
default:
ASSERT(false);
return(-1);
}
/* and configure */
rCDMA_FILTER_CSR(cp->c_filter) = fcsr;
/*
* It's safe to turn the channel clock off now if it was off when we started.
*/
cdma_clock_enable(dma_channel, clock_state);
}
return(0);
}
bool
cdma_clock_enable(int channel, bool state)
{
bool previous = true;
#if CDMA_VERSION > 1
int bank;
int bit;
bank = channel / 32;
bit = channel % 32;
/* save the previous state for our caller */
previous = rCDMA_CLOCK_STATUS(bank) & (1 << bit);
/* frob clock as requested */
if (state != previous) {
if (state) {
rCDMA_CLOCK_ON(bank) = 1 << bit;
} else {
rCDMA_CLOCK_OFF(bank) = 1 << bit;
}
}
#endif
return(previous);
}
static void
cdma_dump_channel(int channel)
{
struct cdma_channel *cp;
struct cdma_command *cmd;
struct dma_segment *seg;
int i;
u_int32_t resid;
int filter;
bool clock_state;
cp = CDMA_SOFTC(channel);
if (CDMA_STATUS_UNINIT == cp->c_status) {
dprintf(DEBUG_CRITICAL, "CDMA: channel %d not set up\n", channel);
return;
}
clock_state = cdma_clock_enable(channel, true);
dprintf(DEBUG_CRITICAL, "CDMA: channel %d %s(%d), index %d resid 0x%x\n",
channel,
((cp->c_status == CDMA_STATUS_IDLE) ? "idle" : ((cp->c_status == CDMA_STATUS_BUSY) ? "busy" : "unknown")),
cp->c_status, cp->c_index, cp->c_resid);
if (cp->c_status != CDMA_STATUS_IDLE) {
dprintf(DEBUG_CRITICAL, "CDMA: channel %d length 0x%x\n", channel, cp->c_length);
resid = cp->c_length;
seg = cp->c_sgl;
dprintf(DEBUG_CRITICAL, "CDMA: seg addr/seg length\n");
for (i = 0; ; i++) {
dprintf(DEBUG_CRITICAL, "CDMA: 0x%08x/0x%x\n", seg->paddr, seg->length);
if (seg->length >= resid)
break;
resid -= seg->length;
seg++;
}
cmd = cp->c_chain;
resid = cp->c_length;
for (i = 0; i < CDMA_CHANNEL_CMDS; i++) {
dprintf(DEBUG_CRITICAL, "CDMA: NXT 0x%08x CMD 0x%08x ADDR 0x%08x LEN 0x%08x\n",
cmd->nxt, cmd->ctrl, cmd->addr, cmd->length);
if ((cmd->ctrl & CDMA_CTRL_CMD_MASK) == CDMA_CTRL_CMD_MEM) {
if (cmd->length >= resid)
break;
resid -= cmd->length;
}
cmd++;
}
}
#if CDMA_VERSION > 1
dprintf(DEBUG_CRITICAL, "CDMA: CSR 0x%08x DCR 0x%08x DAR 0x%08x DBR 0x%08x MAR 0x%08x CAR 0x%08x ERR 0x%08x\n",
rCDMA_CHANNEL_CSR(channel), rCDMA_CHANNEL_DCR(channel), rCDMA_CHANNEL_DAR(channel),
rCDMA_CHANNEL_DBR(channel), rCDMA_CHANNEL_MAR(channel), rCDMA_CHANNEL_CAR(channel),
rCDMA_CHANNEL_ERR(channel) & rCDMA_CHANNEL_ERR_MASK);
#else
dprintf(DEBUG_CRITICAL, "CDMA: CSR 0x%08x DCR 0x%08x DAR 0x%08x DBR 0x%08x MAR 0x%08x CAR 0x%08x\n",
rCDMA_CHANNEL_CSR(channel), rCDMA_CHANNEL_DCR(channel), rCDMA_CHANNEL_DAR(channel),
rCDMA_CHANNEL_DBR(channel), rCDMA_CHANNEL_MAR(channel), rCDMA_CHANNEL_CAR(channel));
#endif
dprintf(DEBUG_CRITICAL, "CDMA: CMD_NXT 0x%08x CMD_CMD 0x%08x CMD_ADDR 0x%08x CMD_LEN 0x%08x\n",
rCDMA_CHANNEL_CMND0(channel), rCDMA_CHANNEL_CMND1(channel), rCDMA_CHANNEL_CMND2(channel),
rCDMA_CHANNEL_CMND3(channel));
filter = (rCDMA_CHANNEL_CSR(channel) >> 8) & 0xff;
if (filter > 0) {
dprintf(DEBUG_CRITICAL, "FILT: CSR 0x%08x\n", rCDMA_FILTER_CSR(filter));
dprintf(DEBUG_CRITICAL, "FILT: IV 0x%08x 0x%08x 0x%08x 0x%08x\n",
rCDMA_FILTER_IVR0(filter),
rCDMA_FILTER_IVR1(filter),
rCDMA_FILTER_IVR2(filter),
rCDMA_FILTER_IVR3(filter));
dprintf(DEBUG_CRITICAL, "FILT: KEY 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
rCDMA_FILTER_KBR0(filter),
rCDMA_FILTER_KBR1(filter),
rCDMA_FILTER_KBR2(filter),
rCDMA_FILTER_KBR3(filter),
rCDMA_FILTER_KBR4(filter),
rCDMA_FILTER_KBR5(filter),
rCDMA_FILTER_KBR6(filter),
rCDMA_FILTER_KBR7(filter));
}
cdma_clock_enable(channel, clock_state);
}
static int
do_cdma(int argc, struct cmd_arg *args)
{
int i;
if (argc != 2) {
dprintf(DEBUG_CRITICAL, "not enough arguments\n");
usage:
dprintf(DEBUG_CRITICAL, "usage:\n");
dprintf(DEBUG_CRITICAL, "%s list - list CDMA channels in use\n", args[0].str);
dprintf(DEBUG_CRITICAL, "%s <channel> - print status for <channel>\n", args[0].str);
return(-1);
}
if (!strcmp(args[1].str, "list")) {
dprintf(DEBUG_CRITICAL, "CDMA channels in use:");
for (i = 1; i <= DMA_CHANNEL_COUNT; i++)
if (CDMA_STATUS_UNINIT != CDMA_SOFTC(i)->c_status)
dprintf(DEBUG_CRITICAL, " %d", i);
dprintf(DEBUG_CRITICAL, "\n");
} else if ((args[1].u > 0) && (args[1].u <= DMA_CHANNEL_COUNT)) {
cdma_dump_channel(args[1].u);
} else {
goto usage;
}
return(0);
}
MENU_COMMAND_DEBUG(cdma, do_cdma, "CDMA status", NULL);
static void
do_cdma_panic(void *arg __unused)
{
int i;
for (i = 1; i <= DMA_CHANNEL_COUNT; i++)
if (CDMA_STATUS_UNINIT != CDMA_SOFTC(i)->c_status)
cdma_dump_channel(i);
}
PANIC_HOOK(cdma, do_cdma_panic, NULL);