blob: 269cc11d30557b7501a224f36c859399197955fe [file] [log] [blame]
/*
* Copyright (C) 2005 - 2008 ServerEngines
* All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation. The full GNU General
* Public License is included in this distribution in the file called COPYING.
*
* Contact Information:
* linux-drivers@serverengines.com
*
* ServerEngines
* 209 N. Fair Oaks Ave
* Sunnyvale, CA 94085
*/
#include <linux/delay.h>
#include "hwlib.h"
#include "bestatus.h"
static
inline void mp_ring_create(struct mp_ring *ring, u32 num, u32 size, void *va)
{
ASSERT(ring);
memset(ring, 0, sizeof(struct mp_ring));
ring->num = num;
ring->pages = DIV_ROUND_UP(num * size, PAGE_SIZE);
ring->itemSize = size;
ring->va = va;
}
/*
* -----------------------------------------------------------------------
* Interface for 2 index rings. i.e. consumer/producer rings
* --------------------------------------------------------------------------
*/
/* Returns number items pending on ring. */
static inline u32 mp_ring_num_pending(struct mp_ring *ring)
{
ASSERT(ring);
if (ring->num == 0)
return 0;
return be_subc(ring->pidx, ring->cidx, ring->num);
}
/* Returns number items free on ring. */
static inline u32 mp_ring_num_empty(struct mp_ring *ring)
{
ASSERT(ring);
return ring->num - 1 - mp_ring_num_pending(ring);
}
/* Consume 1 item */
static inline void mp_ring_consume(struct mp_ring *ring)
{
ASSERT(ring);
ASSERT(ring->pidx != ring->cidx);
ring->cidx = be_addc(ring->cidx, 1, ring->num);
}
/* Produce 1 item */
static inline void mp_ring_produce(struct mp_ring *ring)
{
ASSERT(ring);
ring->pidx = be_addc(ring->pidx, 1, ring->num);
}
/* Consume count items */
static inline void mp_ring_consume_multiple(struct mp_ring *ring, u32 count)
{
ASSERT(ring);
ASSERT(mp_ring_num_pending(ring) >= count);
ring->cidx = be_addc(ring->cidx, count, ring->num);
}
static inline void *mp_ring_item(struct mp_ring *ring, u32 index)
{
ASSERT(ring);
ASSERT(index < ring->num);
ASSERT(ring->itemSize > 0);
return (u8 *) ring->va + index * ring->itemSize;
}
/* Ptr to produce item */
static inline void *mp_ring_producer_ptr(struct mp_ring *ring)
{
ASSERT(ring);
return mp_ring_item(ring, ring->pidx);
}
/*
* Returns a pointer to the current location in the ring.
* This is used for rings with 1 index.
*/
static inline void *mp_ring_current(struct mp_ring *ring)
{
ASSERT(ring);
ASSERT(ring->pidx == 0); /* not used */
return mp_ring_item(ring, ring->cidx);
}
/*
* Increment index for rings with only 1 index.
* This is used for rings with 1 index.
*/
static inline void *mp_ring_next(struct mp_ring *ring)
{
ASSERT(ring);
ASSERT(ring->num > 0);
ASSERT(ring->pidx == 0); /* not used */
ring->cidx = be_addc(ring->cidx, 1, ring->num);
return mp_ring_current(ring);
}
/*
This routine waits for a previously posted mailbox WRB to be completed.
Specifically it waits for the mailbox to say that it's ready to accept
more data by setting the LSB of the mailbox pd register to 1.
pcontroller - The function object to post this data to
IRQL < DISPATCH_LEVEL
*/
static void be_mcc_mailbox_wait(struct be_function_object *pfob)
{
struct MPU_MAILBOX_DB_AMAP mailbox_db;
u32 i = 0;
u32 ready;
if (pfob->emulate) {
/* No waiting for mailbox in emulated mode. */
return;
}
mailbox_db.dw[0] = PD_READ(pfob, mcc_bootstrap_db);
ready = AMAP_GET_BITS_PTR(MPU_MAILBOX_DB, ready, &mailbox_db);
while (ready == false) {
if ((++i & 0x3FFFF) == 0) {
TRACE(DL_WARN, "Waiting for mailbox ready - %dk polls",
i / 1000);
}
udelay(1);
mailbox_db.dw[0] = PD_READ(pfob, mcc_bootstrap_db);
ready = AMAP_GET_BITS_PTR(MPU_MAILBOX_DB, ready, &mailbox_db);
}
}
/*
This routine tells the MCC mailbox that there is data to processed
in the mailbox. It does this by setting the physical address for the
mailbox location and clearing the LSB. This routine returns immediately
and does not wait for the WRB to be processed.
pcontroller - The function object to post this data to
IRQL < DISPATCH_LEVEL
*/
static void be_mcc_mailbox_notify(struct be_function_object *pfob)
{
struct MPU_MAILBOX_DB_AMAP mailbox_db;
u32 pa;
ASSERT(pfob->mailbox.pa);
ASSERT(pfob->mailbox.va);
/* If emulated, do not ring the mailbox */
if (pfob->emulate) {
TRACE(DL_WARN, "MPU disabled. Skipping mailbox notify.");
return;
}
/* form the higher bits in the address */
mailbox_db.dw[0] = 0; /* init */
AMAP_SET_BITS_PTR(MPU_MAILBOX_DB, hi, &mailbox_db, 1);
AMAP_SET_BITS_PTR(MPU_MAILBOX_DB, ready, &mailbox_db, 0);
/* bits 34 to 63 */
pa = (u32) (pfob->mailbox.pa >> 34);
AMAP_SET_BITS_PTR(MPU_MAILBOX_DB, address, &mailbox_db, pa);
/* Wait for the MPU to be ready */
be_mcc_mailbox_wait(pfob);
/* Ring doorbell 1st time */
PD_WRITE(pfob, mcc_bootstrap_db, mailbox_db.dw[0]);
/* Wait for 1st write to be acknowledged. */
be_mcc_mailbox_wait(pfob);
/* lower bits 30 bits from 4th bit (bits 4 to 33)*/
pa = (u32) (pfob->mailbox.pa >> 4) & 0x3FFFFFFF;
AMAP_SET_BITS_PTR(MPU_MAILBOX_DB, hi, &mailbox_db, 0);
AMAP_SET_BITS_PTR(MPU_MAILBOX_DB, ready, &mailbox_db, 0);
AMAP_SET_BITS_PTR(MPU_MAILBOX_DB, address, &mailbox_db, pa);
/* Ring doorbell 2nd time */
PD_WRITE(pfob, mcc_bootstrap_db, mailbox_db.dw[0]);
}
/*
This routine tells the MCC mailbox that there is data to processed
in the mailbox. It does this by setting the physical address for the
mailbox location and clearing the LSB. This routine spins until the
MPU writes a 1 into the LSB indicating that the data has been received
and is ready to be processed.
pcontroller - The function object to post this data to
IRQL < DISPATCH_LEVEL
*/
static void
be_mcc_mailbox_notify_and_wait(struct be_function_object *pfob)
{
/*
* Notify it
*/
be_mcc_mailbox_notify(pfob);
/*
* Now wait for completion of WRB
*/
be_mcc_mailbox_wait(pfob);
}
void
be_mcc_process_cqe(struct be_function_object *pfob,
struct MCC_CQ_ENTRY_AMAP *cqe)
{
struct be_mcc_wrb_context *wrb_context = NULL;
u32 offset, status;
u8 *p;
ASSERT(cqe);
/*
* A command completed. Commands complete out-of-order.
* Determine which command completed from the TAG.
*/
offset = offsetof(struct BE_MCC_CQ_ENTRY_AMAP, mcc_tag)/8;
p = (u8 *) cqe + offset;
wrb_context = (struct be_mcc_wrb_context *)(void *)(size_t)(*(u64 *)p);
ASSERT(wrb_context);
/*
* Perform a response copy if requested.
* Only copy data if the FWCMD is successful.
*/
status = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY, completion_status, cqe);
if (status == MGMT_STATUS_SUCCESS && wrb_context->copy.length > 0) {
ASSERT(wrb_context->wrb);
ASSERT(wrb_context->copy.va);
p = (u8 *)wrb_context->wrb +
offsetof(struct BE_MCC_WRB_AMAP, payload)/8;
memcpy(wrb_context->copy.va,
(u8 *)p + wrb_context->copy.fwcmd_offset,
wrb_context->copy.length);
}
if (status)
status = BE_NOT_OK;
/* internal callback */
if (wrb_context->internal_cb) {
wrb_context->internal_cb(wrb_context->internal_cb_context,
status, wrb_context->wrb);
}
/* callback */
if (wrb_context->cb) {
wrb_context->cb(wrb_context->cb_context,
status, wrb_context->wrb);
}
/* Free the context structure */
_be_mcc_free_wrb_context(pfob, wrb_context);
}
void be_drive_mcc_wrb_queue(struct be_mcc_object *mcc)
{
struct be_function_object *pfob = NULL;
int status = BE_PENDING;
struct be_generic_q_ctxt *q_ctxt;
struct MCC_WRB_AMAP *wrb;
struct MCC_WRB_AMAP *queue_wrb;
u32 length, payload_length, sge_count, embedded;
unsigned long irql;
BUILD_BUG_ON((sizeof(struct be_generic_q_ctxt) <
sizeof(struct be_queue_driver_context) +
sizeof(struct MCC_WRB_AMAP)));
pfob = mcc->parent_function;
spin_lock_irqsave(&pfob->post_lock, irql);
if (mcc->driving_backlog) {
spin_unlock_irqrestore(&pfob->post_lock, irql);
if (pfob->pend_queue_driving && pfob->mcc) {
pfob->pend_queue_driving = 0;
be_drive_mcc_wrb_queue(pfob->mcc);
}
return;
}
/* Acquire the flag to limit 1 thread to redrive posts. */
mcc->driving_backlog = 1;
while (!list_empty(&mcc->backlog)) {
wrb = _be_mpu_peek_ring_wrb(mcc, true); /* Driving the queue */
if (!wrb)
break; /* No space in the ring yet. */
/* Get the next queued entry to process. */
q_ctxt = list_first_entry(&mcc->backlog,
struct be_generic_q_ctxt, context.list);
list_del(&q_ctxt->context.list);
pfob->mcc->backlog_length--;
/*
* Compute the required length of the WRB.
* Since the queue element may be smaller than
* the complete WRB, copy only the required number of bytes.
*/
queue_wrb = (struct MCC_WRB_AMAP *) &q_ctxt->wrb_header;
embedded = AMAP_GET_BITS_PTR(MCC_WRB, embedded, queue_wrb);
if (embedded) {
payload_length = AMAP_GET_BITS_PTR(MCC_WRB,
payload_length, queue_wrb);
length = sizeof(struct be_mcc_wrb_header) +
payload_length;
} else {
sge_count = AMAP_GET_BITS_PTR(MCC_WRB, sge_count,
queue_wrb);
ASSERT(sge_count == 1); /* only 1 frag. */
length = sizeof(struct be_mcc_wrb_header) +
sge_count * sizeof(struct MCC_SGE_AMAP);
}
/*
* Truncate the length based on the size of the
* queue element. Some elements that have output parameters
* can be smaller than the payload_length field would
* indicate. We really only need to copy the request
* parameters, not the response.
*/
length = min(length, (u32) (q_ctxt->context.bytes -
offsetof(struct be_generic_q_ctxt, wrb_header)));
/* Copy the queue element WRB into the ring. */
memcpy(wrb, &q_ctxt->wrb_header, length);
/* Post the wrb. This should not fail assuming we have
* enough context structs. */
status = be_function_post_mcc_wrb(pfob, wrb, NULL,
q_ctxt->context.cb, q_ctxt->context.cb_context,
q_ctxt->context.internal_cb,
q_ctxt->context.internal_cb_context,
q_ctxt->context.optional_fwcmd_va,
&q_ctxt->context.copy);
if (status == BE_SUCCESS) {
/*
* Synchronous completion. Since it was queued,
* we will invoke the callback.
* To the user, this is an asynchronous request.
*/
spin_unlock_irqrestore(&pfob->post_lock, irql);
if (pfob->pend_queue_driving && pfob->mcc) {
pfob->pend_queue_driving = 0;
be_drive_mcc_wrb_queue(pfob->mcc);
}
ASSERT(q_ctxt->context.cb);
q_ctxt->context.cb(
q_ctxt->context.cb_context,
BE_SUCCESS, NULL);
spin_lock_irqsave(&pfob->post_lock, irql);
} else if (status != BE_PENDING) {
/*
* Another resource failed. Should never happen
* if we have sufficient MCC_WRB_CONTEXT structs.
* Return to head of the queue.
*/
TRACE(DL_WARN, "Failed to post a queued WRB. 0x%x",
status);
list_add(&q_ctxt->context.list, &mcc->backlog);
pfob->mcc->backlog_length++;
break;
}
}
/* Free the flag to limit 1 thread to redrive posts. */
mcc->driving_backlog = 0;
spin_unlock_irqrestore(&pfob->post_lock, irql);
}
/* This function asserts that the WRB was consumed in order. */
#ifdef BE_DEBUG
u32 be_mcc_wrb_consumed_in_order(struct be_mcc_object *mcc,
struct MCC_CQ_ENTRY_AMAP *cqe)
{
struct be_mcc_wrb_context *wrb_context = NULL;
u32 wrb_index;
u32 wrb_consumed_in_order;
u32 offset;
u8 *p;
ASSERT(cqe);
/*
* A command completed. Commands complete out-of-order.
* Determine which command completed from the TAG.
*/
offset = offsetof(struct BE_MCC_CQ_ENTRY_AMAP, mcc_tag)/8;
p = (u8 *) cqe + offset;
wrb_context = (struct be_mcc_wrb_context *)(void *)(size_t)(*(u64 *)p);
ASSERT(wrb_context);
wrb_index = (u32) (((u64)(size_t)wrb_context->ring_wrb -
(u64)(size_t)mcc->sq.ring.va) / sizeof(struct MCC_WRB_AMAP));
ASSERT(wrb_index < mcc->sq.ring.num);
wrb_consumed_in_order = (u32) (wrb_index == mcc->consumed_index);
mcc->consumed_index = be_addc(mcc->consumed_index, 1, mcc->sq.ring.num);
return wrb_consumed_in_order;
}
#endif
int be_mcc_process_cq(struct be_mcc_object *mcc, bool rearm)
{
struct be_function_object *pfob = NULL;
struct MCC_CQ_ENTRY_AMAP *cqe;
struct CQ_DB_AMAP db;
struct mp_ring *cq_ring = &mcc->cq.ring;
struct mp_ring *mp_ring = &mcc->sq.ring;
u32 num_processed = 0;
u32 consumed = 0, valid, completed, cqe_consumed, async_event;
pfob = mcc->parent_function;
spin_lock_irqsave(&pfob->cq_lock, pfob->cq_irq);
/*
* Verify that only one thread is processing the CQ at once.
* We cannot hold the lock while processing the CQ due to
* the callbacks into the OS. Therefore, this flag is used
* to control it. If any of the threads want to
* rearm the CQ, we need to honor that.
*/
if (mcc->processing != 0) {
mcc->rearm = mcc->rearm || rearm;
goto Error;
} else {
mcc->processing = 1; /* lock processing for this thread. */
mcc->rearm = rearm; /* set our rearm setting */
}
spin_unlock_irqrestore(&pfob->cq_lock, pfob->cq_irq);
cqe = mp_ring_current(cq_ring);
valid = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY, valid, cqe);
while (valid) {
if (num_processed >= 8) {
/* coalesce doorbells, but free space in cq
* ring while processing. */
db.dw[0] = 0; /* clear */
AMAP_SET_BITS_PTR(CQ_DB, qid, &db, cq_ring->id);
AMAP_SET_BITS_PTR(CQ_DB, rearm, &db, false);
AMAP_SET_BITS_PTR(CQ_DB, event, &db, false);
AMAP_SET_BITS_PTR(CQ_DB, num_popped, &db,
num_processed);
num_processed = 0;
PD_WRITE(pfob, cq_db, db.dw[0]);
}
async_event = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY, async_event, cqe);
if (async_event) {
/* This is an asynchronous event. */
struct ASYNC_EVENT_TRAILER_AMAP *async_trailer =
(struct ASYNC_EVENT_TRAILER_AMAP *)
((u8 *) cqe + sizeof(struct MCC_CQ_ENTRY_AMAP) -
sizeof(struct ASYNC_EVENT_TRAILER_AMAP));
u32 event_code;
async_event = AMAP_GET_BITS_PTR(ASYNC_EVENT_TRAILER,
async_event, async_trailer);
ASSERT(async_event == 1);
valid = AMAP_GET_BITS_PTR(ASYNC_EVENT_TRAILER,
valid, async_trailer);
ASSERT(valid == 1);
/* Call the async event handler if it is installed. */
if (mcc->async_cb) {
event_code =
AMAP_GET_BITS_PTR(ASYNC_EVENT_TRAILER,
event_code, async_trailer);
mcc->async_cb(mcc->async_context,
(u32) event_code, (void *) cqe);
}
} else {
/* This is a completion entry. */
/* No vm forwarding in this driver. */
cqe_consumed = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY,
consumed, cqe);
if (cqe_consumed) {
/*
* A command on the MCC ring was consumed.
* Update the consumer index.
* These occur in order.
*/
ASSERT(be_mcc_wrb_consumed_in_order(mcc, cqe));
consumed++;
}
completed = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY,
completed, cqe);
if (completed) {
/* A command completed. Use tag to
* determine which command. */
be_mcc_process_cqe(pfob, cqe);
}
}
/* Reset the CQE */
AMAP_SET_BITS_PTR(MCC_CQ_ENTRY, valid, cqe, false);
num_processed++;
/* Update our tracking for the CQ ring. */
cqe = mp_ring_next(cq_ring);
valid = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY, valid, cqe);
}
TRACE(DL_INFO, "num_processed:0x%x, and consumed:0x%x",
num_processed, consumed);
/*
* Grab the CQ lock to synchronize the "rearm" setting for
* the doorbell, and for clearing the "processing" flag.
*/
spin_lock_irqsave(&pfob->cq_lock, pfob->cq_irq);
/*
* Rearm the cq. This is done based on the global mcc->rearm
* flag which combines the rearm parameter from the current
* call to process_cq and any other threads
* that tried to process the CQ while this one was active.
* This handles the situation where a sync. fwcmd was processing
* the CQ while the interrupt/dpc tries to process it.
* The sync process gets to continue -- but it is now
* responsible for the rearming.
*/
if (num_processed > 0 || mcc->rearm == true) {
db.dw[0] = 0; /* clear */
AMAP_SET_BITS_PTR(CQ_DB, qid, &db, cq_ring->id);
AMAP_SET_BITS_PTR(CQ_DB, rearm, &db, mcc->rearm);
AMAP_SET_BITS_PTR(CQ_DB, event, &db, false);
AMAP_SET_BITS_PTR(CQ_DB, num_popped, &db, num_processed);
PD_WRITE(pfob, cq_db, db.dw[0]);
}
/*
* Update the consumer index after ringing the CQ doorbell.
* We don't want another thread to post more WRBs before we
* have CQ space available.
*/
mp_ring_consume_multiple(mp_ring, consumed);
/* Clear the processing flag. */
mcc->processing = 0;
Error:
spin_unlock_irqrestore(&pfob->cq_lock, pfob->cq_irq);
/*
* Use the local variable to detect if the current thread
* holds the WRB post lock. If rearm is false, this is
* either a synchronous command, or the upper layer driver is polling
* from a thread. We do not drive the queue from that
* context since the driver may hold the
* wrb post lock already.
*/
if (rearm)
be_drive_mcc_wrb_queue(mcc);
else
pfob->pend_queue_driving = 1;
return BE_SUCCESS;
}
/*
*============================================================================
* P U B L I C R O U T I N E S
*============================================================================
*/
/*
This routine creates an MCC object. This object contains an MCC send queue
and a CQ private to the MCC.
pcontroller - Handle to a function object
EqObject - EQ object that will be used to dispatch this MCC
ppMccObject - Pointer to an internal Mcc Object returned.
Returns BE_SUCCESS if successfull,, otherwise a useful error code
is returned.
IRQL < DISPATCH_LEVEL
*/
int
be_mcc_ring_create(struct be_function_object *pfob,
struct ring_desc *rd, u32 length,
struct be_mcc_wrb_context *context_array,
u32 num_context_entries,
struct be_cq_object *cq, struct be_mcc_object *mcc)
{
int status = 0;
struct FWCMD_COMMON_MCC_CREATE *fwcmd = NULL;
struct MCC_WRB_AMAP *wrb = NULL;
u32 num_entries_encoded, n, i;
void *va = NULL;
unsigned long irql;
if (length < sizeof(struct MCC_WRB_AMAP) * 2) {
TRACE(DL_ERR, "Invalid MCC ring length:%d", length);
return BE_NOT_OK;
}
/*
* Reduce the actual ring size to be less than the number
* of context entries. This ensures that we run out of
* ring WRBs first so the queuing works correctly. We never
* queue based on context structs.
*/
if (num_context_entries + 1 <
length / sizeof(struct MCC_WRB_AMAP) - 1) {
u32 max_length =
(num_context_entries + 2) * sizeof(struct MCC_WRB_AMAP);
if (is_power_of_2(max_length))
length = __roundup_pow_of_two(max_length+1) / 2;
else
length = __roundup_pow_of_two(max_length) / 2;
ASSERT(length <= max_length);
TRACE(DL_WARN,
"MCC ring length reduced based on context entries."
" length:%d wrbs:%d context_entries:%d", length,
(int) (length / sizeof(struct MCC_WRB_AMAP)),
num_context_entries);
}
spin_lock_irqsave(&pfob->post_lock, irql);
num_entries_encoded =
be_ring_length_to_encoding(length, sizeof(struct MCC_WRB_AMAP));
/* Init MCC object. */
memset(mcc, 0, sizeof(*mcc));
mcc->parent_function = pfob;
mcc->cq_object = cq;
INIT_LIST_HEAD(&mcc->backlog);
wrb = be_function_peek_mcc_wrb(pfob);
if (!wrb) {
ASSERT(wrb);
TRACE(DL_ERR, "No free MCC WRBs in create EQ.");
status = BE_STATUS_NO_MCC_WRB;
goto error;
}
/* Prepares an embedded fwcmd, including request/response sizes. */
fwcmd = BE_PREPARE_EMBEDDED_FWCMD(pfob, wrb, COMMON_MCC_CREATE);
fwcmd->params.request.num_pages = DIV_ROUND_UP(length, PAGE_SIZE);
/*
* Program MCC ring context
*/
AMAP_SET_BITS_PTR(MCC_RING_CONTEXT, pdid,
&fwcmd->params.request.context, 0);
AMAP_SET_BITS_PTR(MCC_RING_CONTEXT, invalid,
&fwcmd->params.request.context, false);
AMAP_SET_BITS_PTR(MCC_RING_CONTEXT, ring_size,
&fwcmd->params.request.context, num_entries_encoded);
n = cq->cq_id;
AMAP_SET_BITS_PTR(MCC_RING_CONTEXT,
cq_id, &fwcmd->params.request.context, n);
be_rd_to_pa_list(rd, fwcmd->params.request.pages,
ARRAY_SIZE(fwcmd->params.request.pages));
/* Post the f/w command */
status = be_function_post_mcc_wrb(pfob, wrb, NULL, NULL, NULL,
NULL, NULL, fwcmd, NULL);
if (status != BE_SUCCESS) {
TRACE(DL_ERR, "MCC to create CQ failed.");
goto error;
}
/*
* Create a linked list of context structures
*/
mcc->wrb_context.base = context_array;
mcc->wrb_context.num = num_context_entries;
INIT_LIST_HEAD(&mcc->wrb_context.list_head);
memset(context_array, 0,
sizeof(struct be_mcc_wrb_context) * num_context_entries);
for (i = 0; i < mcc->wrb_context.num; i++) {
list_add_tail(&context_array[i].next,
&mcc->wrb_context.list_head);
}
/*
*
* Create an mcc_ring for tracking WRB hw ring
*/
va = rd->va;
ASSERT(va);
mp_ring_create(&mcc->sq.ring, length / sizeof(struct MCC_WRB_AMAP),
sizeof(struct MCC_WRB_AMAP), va);
mcc->sq.ring.id = fwcmd->params.response.id;
/*
* Init a mcc_ring for tracking the MCC CQ.
*/
ASSERT(cq->va);
mp_ring_create(&mcc->cq.ring, cq->num_entries,
sizeof(struct MCC_CQ_ENTRY_AMAP), cq->va);
mcc->cq.ring.id = cq->cq_id;
/* Force zeroing of CQ. */
memset(cq->va, 0, cq->num_entries * sizeof(struct MCC_CQ_ENTRY_AMAP));
/* Initialize debug index. */
mcc->consumed_index = 0;
atomic_inc(&cq->ref_count);
pfob->mcc = mcc;
TRACE(DL_INFO, "MCC ring created. id:%d bytes:%d cq_id:%d cq_entries:%d"
" num_context:%d", mcc->sq.ring.id, length,
cq->cq_id, cq->num_entries, num_context_entries);
error:
spin_unlock_irqrestore(&pfob->post_lock, irql);
if (pfob->pend_queue_driving && pfob->mcc) {
pfob->pend_queue_driving = 0;
be_drive_mcc_wrb_queue(pfob->mcc);
}
return status;
}
/*
This routine destroys an MCC send queue
MccObject - Internal Mcc Object to be destroyed.
Returns BE_SUCCESS if successfull, otherwise an error code is returned.
IRQL < DISPATCH_LEVEL
The caller of this routine must ensure that no other WRB may be posted
until this routine returns.
*/
int be_mcc_ring_destroy(struct be_mcc_object *mcc)
{
int status = 0;
struct be_function_object *pfob = mcc->parent_function;
ASSERT(mcc->processing == 0);
/*
* Remove the ring from the function object.
* This transitions back to mailbox mode.
*/
pfob->mcc = NULL;
/* Send fwcmd to destroy the queue. (Using the mailbox.) */
status = be_function_ring_destroy(mcc->parent_function, mcc->sq.ring.id,
FWCMD_RING_TYPE_MCC, NULL, NULL, NULL, NULL);
ASSERT(status == 0);
/* Release the SQ reference to the CQ */
atomic_dec(&mcc->cq_object->ref_count);
return status;
}
static void
mcc_wrb_sync_cb(void *context, int staus, struct MCC_WRB_AMAP *wrb)
{
struct be_mcc_wrb_context *wrb_context =
(struct be_mcc_wrb_context *) context;
ASSERT(wrb_context);
*wrb_context->users_final_status = staus;
}
/*
This routine posts a command to the MCC send queue
mcc - Internal Mcc Object to be destroyed.
wrb - wrb to post.
Returns BE_SUCCESS if successfull, otherwise an error code is returned.
IRQL < DISPATCH_LEVEL if CompletionCallback is not NULL
IRQL <=DISPATCH_LEVEL if CompletionCallback is NULL
If this routine is called with CompletionCallback != NULL the
call is considered to be asynchronous and will return as soon
as the WRB is posted to the MCC with BE_PENDING.
If CompletionCallback is NULL, then this routine will not return until
a completion for this MCC command has been processed.
If called at DISPATCH_LEVEL the CompletionCallback must be NULL.
This routine should only be called if the MPU has been boostraped past
mailbox mode.
*/
int
_be_mpu_post_wrb_ring(struct be_mcc_object *mcc, struct MCC_WRB_AMAP *wrb,
struct be_mcc_wrb_context *wrb_context)
{
struct MCC_WRB_AMAP *ring_wrb = NULL;
int status = BE_PENDING;
int final_status = BE_PENDING;
mcc_wrb_cqe_callback cb = NULL;
struct MCC_DB_AMAP mcc_db;
u32 embedded;
ASSERT(mp_ring_num_empty(&mcc->sq.ring) > 0);
/*
* Input wrb is most likely the next wrb in the ring, since the client
* can peek at the address.
*/
ring_wrb = mp_ring_producer_ptr(&mcc->sq.ring);
if (wrb != ring_wrb) {
/* If not equal, copy it into the ring. */
memcpy(ring_wrb, wrb, sizeof(struct MCC_WRB_AMAP));
}
#ifdef BE_DEBUG
wrb_context->ring_wrb = ring_wrb;
#endif
embedded = AMAP_GET_BITS_PTR(MCC_WRB, embedded, ring_wrb);
if (embedded) {
/* embedded commands will have the response within the WRB. */
wrb_context->wrb = ring_wrb;
} else {
/*
* non-embedded commands will not have the response
* within the WRB, and they may complete out-of-order.
* The WRB will not be valid to inspect
* during the completion.
*/
wrb_context->wrb = NULL;
}
cb = wrb_context->cb;
if (cb == NULL) {
/* Assign our internal callback if this is a
* synchronous call. */
wrb_context->cb = mcc_wrb_sync_cb;
wrb_context->cb_context = wrb_context;
wrb_context->users_final_status = &final_status;
}
/* Increment producer index */
mcc_db.dw[0] = 0; /* initialize */
AMAP_SET_BITS_PTR(MCC_DB, rid, &mcc_db, mcc->sq.ring.id);
AMAP_SET_BITS_PTR(MCC_DB, numPosted, &mcc_db, 1);
mp_ring_produce(&mcc->sq.ring);
PD_WRITE(mcc->parent_function, mpu_mcc_db, mcc_db.dw[0]);
TRACE(DL_INFO, "pidx: %x and cidx: %x.", mcc->sq.ring.pidx,
mcc->sq.ring.cidx);
if (cb == NULL) {
int polls = 0; /* At >= 1 us per poll */
/* Wait until this command completes, polling the CQ. */
do {
TRACE(DL_INFO, "FWCMD submitted in the poll mode.");
/* Do not rearm CQ in this context. */
be_mcc_process_cq(mcc, false);
if (final_status == BE_PENDING) {
if ((++polls & 0x7FFFF) == 0) {
TRACE(DL_WARN,
"Warning : polling MCC CQ for %d"
"ms.", polls / 1000);
}
udelay(1);
}
/* final_status changed when the command completes */
} while (final_status == BE_PENDING);
status = final_status;
}
return status;
}
struct MCC_WRB_AMAP *
_be_mpu_peek_ring_wrb(struct be_mcc_object *mcc, bool driving_queue)
{
/* If we have queued items, do not allow a post to bypass the queue. */
if (!driving_queue && !list_empty(&mcc->backlog))
return NULL;
if (mp_ring_num_empty(&mcc->sq.ring) <= 0)
return NULL;
return (struct MCC_WRB_AMAP *) mp_ring_producer_ptr(&mcc->sq.ring);
}
int
be_mpu_init_mailbox(struct be_function_object *pfob, struct ring_desc *mailbox)
{
ASSERT(mailbox);
pfob->mailbox.va = mailbox->va;
pfob->mailbox.pa = cpu_to_le64(mailbox->pa);
pfob->mailbox.length = mailbox->length;
ASSERT(((u32)(size_t)pfob->mailbox.va & 0xf) == 0);
ASSERT(((u32)(size_t)pfob->mailbox.pa & 0xf) == 0);
/*
* Issue the WRB to set MPU endianness
*/
{
u64 *endian_check = (u64 *) (pfob->mailbox.va +
offsetof(struct BE_MCC_MAILBOX_AMAP, wrb)/8);
*endian_check = 0xFF1234FFFF5678FFULL;
}
be_mcc_mailbox_notify_and_wait(pfob);
return BE_SUCCESS;
}
/*
This routine posts a command to the MCC mailbox.
FuncObj - Function Object to post the WRB on behalf of.
wrb - wrb to post.
CompletionCallback - Address of a callback routine to invoke once the WRB
is completed.
CompletionCallbackContext - Opaque context to be passed during the call to
the CompletionCallback.
Returns BE_SUCCESS if successfull, otherwise an error code is returned.
IRQL <=DISPATCH_LEVEL if CompletionCallback is NULL
This routine will block until a completion for this MCC command has been
processed. If called at DISPATCH_LEVEL the CompletionCallback must be NULL.
This routine should only be called if the MPU has not been boostraped past
mailbox mode.
*/
int
_be_mpu_post_wrb_mailbox(struct be_function_object *pfob,
struct MCC_WRB_AMAP *wrb, struct be_mcc_wrb_context *wrb_context)
{
struct MCC_MAILBOX_AMAP *mailbox = NULL;
struct MCC_WRB_AMAP *mb_wrb;
struct MCC_CQ_ENTRY_AMAP *mb_cq;
u32 offset, status;
ASSERT(pfob->mcc == NULL);
mailbox = pfob->mailbox.va;
ASSERT(mailbox);
offset = offsetof(struct BE_MCC_MAILBOX_AMAP, wrb)/8;
mb_wrb = (struct MCC_WRB_AMAP *) (u8 *)mailbox + offset;
if (mb_wrb != wrb) {
memset(mailbox, 0, sizeof(*mailbox));
memcpy(mb_wrb, wrb, sizeof(struct MCC_WRB_AMAP));
}
/* The callback can inspect the final WRB to get output parameters. */
wrb_context->wrb = mb_wrb;
be_mcc_mailbox_notify_and_wait(pfob);
/* A command completed. Use tag to determine which command. */
offset = offsetof(struct BE_MCC_MAILBOX_AMAP, cq)/8;
mb_cq = (struct MCC_CQ_ENTRY_AMAP *) ((u8 *)mailbox + offset);
be_mcc_process_cqe(pfob, mb_cq);
status = AMAP_GET_BITS_PTR(MCC_CQ_ENTRY, completion_status, mb_cq);
if (status)
status = BE_NOT_OK;
return status;
}
struct be_mcc_wrb_context *
_be_mcc_allocate_wrb_context(struct be_function_object *pfob)
{
struct be_mcc_wrb_context *context = NULL;
unsigned long irq;
spin_lock_irqsave(&pfob->mcc_context_lock, irq);
if (!pfob->mailbox.default_context_allocated) {
/* Use the single default context that we
* always have allocated. */
pfob->mailbox.default_context_allocated = true;
context = &pfob->mailbox.default_context;
} else if (pfob->mcc) {
/* Get a context from the free list. If any are available. */
if (!list_empty(&pfob->mcc->wrb_context.list_head)) {
context = list_first_entry(
&pfob->mcc->wrb_context.list_head,
struct be_mcc_wrb_context, next);
}
}
spin_unlock_irqrestore(&pfob->mcc_context_lock, irq);
return context;
}
void
_be_mcc_free_wrb_context(struct be_function_object *pfob,
struct be_mcc_wrb_context *context)
{
unsigned long irq;
ASSERT(context);
/*
* Zero during free to try and catch any bugs where the context
* is accessed after a free.
*/
memset(context, 0, sizeof(context));
spin_lock_irqsave(&pfob->mcc_context_lock, irq);
if (context == &pfob->mailbox.default_context) {
/* Free the default context. */
ASSERT(pfob->mailbox.default_context_allocated);
pfob->mailbox.default_context_allocated = false;
} else {
/* Add to free list. */
ASSERT(pfob->mcc);
list_add_tail(&context->next,
&pfob->mcc->wrb_context.list_head);
}
spin_unlock_irqrestore(&pfob->mcc_context_lock, irq);
}
int
be_mcc_add_async_event_callback(struct be_mcc_object *mcc_object,
mcc_async_event_callback cb, void *cb_context)
{
/* Lock against anyone trying to change the callback/context pointers
* while being used. */
spin_lock_irqsave(&mcc_object->parent_function->cq_lock,
mcc_object->parent_function->cq_irq);
/* Assign the async callback. */
mcc_object->async_context = cb_context;
mcc_object->async_cb = cb;
spin_unlock_irqrestore(&mcc_object->parent_function->cq_lock,
mcc_object->parent_function->cq_irq);
return BE_SUCCESS;
}
#define MPU_EP_CONTROL 0
#define MPU_EP_SEMAPHORE 0xac
/*
*-------------------------------------------------------------------
* Function: be_wait_for_POST_complete
* Waits until the BladeEngine POST completes (either in error or success).
* pfob -
* return status - BE_SUCCESS (0) on success. Negative error code on failure.
*-------------------------------------------------------------------
*/
static int be_wait_for_POST_complete(struct be_function_object *pfob)
{
struct MGMT_HBA_POST_STATUS_STRUCT_AMAP status;
int s;
u32 post_error, post_stage;
const u32 us_per_loop = 1000; /* 1000us */
const u32 print_frequency_loops = 1000000 / us_per_loop;
const u32 max_loops = 60 * print_frequency_loops;
u32 loops = 0;
/*
* Wait for arm fw indicating it is done or a fatal error happened.
* Note: POST can take some time to complete depending on configuration
* settings (consider ARM attempts to acquire an IP address
* over DHCP!!!).
*
*/
do {
status.dw[0] = ioread32(pfob->csr_va + MPU_EP_SEMAPHORE);
post_error = AMAP_GET_BITS_PTR(MGMT_HBA_POST_STATUS_STRUCT,
error, &status);
post_stage = AMAP_GET_BITS_PTR(MGMT_HBA_POST_STATUS_STRUCT,
stage, &status);
if (0 == (loops % print_frequency_loops)) {
/* Print current status */
TRACE(DL_INFO, "POST status = 0x%x (stage = 0x%x)",
status.dw[0], post_stage);
}
udelay(us_per_loop);
} while ((post_error != 1) &&
(post_stage != POST_STAGE_ARMFW_READY) &&
(++loops < max_loops));
if (post_error == 1) {
TRACE(DL_ERR, "POST error! Status = 0x%x (stage = 0x%x)",
status.dw[0], post_stage);
s = BE_NOT_OK;
} else if (post_stage != POST_STAGE_ARMFW_READY) {
TRACE(DL_ERR, "POST time-out! Status = 0x%x (stage = 0x%x)",
status.dw[0], post_stage);
s = BE_NOT_OK;
} else {
s = BE_SUCCESS;
}
return s;
}
/*
*-------------------------------------------------------------------
* Function: be_kickoff_and_wait_for_POST
* Interacts with the BladeEngine management processor to initiate POST, and
* subsequently waits until POST completes (either in error or success).
* The caller must acquire the reset semaphore before initiating POST
* to prevent multiple drivers interacting with the management processor.
* Once POST is complete the caller must release the reset semaphore.
* Callers who only want to wait for POST complete may call
* be_wait_for_POST_complete.
* pfob -
* return status - BE_SUCCESS (0) on success. Negative error code on failure.
*-------------------------------------------------------------------
*/
static int
be_kickoff_and_wait_for_POST(struct be_function_object *pfob)
{
struct MGMT_HBA_POST_STATUS_STRUCT_AMAP status;
int s;
const u32 us_per_loop = 1000; /* 1000us */
const u32 print_frequency_loops = 1000000 / us_per_loop;
const u32 max_loops = 5 * print_frequency_loops;
u32 loops = 0;
u32 post_error, post_stage;
/* Wait for arm fw awaiting host ready or a fatal error happened. */
TRACE(DL_INFO, "Wait for BladeEngine ready to POST");
do {
status.dw[0] = ioread32(pfob->csr_va + MPU_EP_SEMAPHORE);
post_error = AMAP_GET_BITS_PTR(MGMT_HBA_POST_STATUS_STRUCT,
error, &status);
post_stage = AMAP_GET_BITS_PTR(MGMT_HBA_POST_STATUS_STRUCT,
stage, &status);
if (0 == (loops % print_frequency_loops)) {
/* Print current status */
TRACE(DL_INFO, "POST status = 0x%x (stage = 0x%x)",
status.dw[0], post_stage);
}
udelay(us_per_loop);
} while ((post_error != 1) &&
(post_stage < POST_STAGE_AWAITING_HOST_RDY) &&
(++loops < max_loops));
if (post_error == 1) {
TRACE(DL_ERR, "Pre-POST error! Status = 0x%x (stage = 0x%x)",
status.dw[0], post_stage);
s = BE_NOT_OK;
} else if (post_stage == POST_STAGE_AWAITING_HOST_RDY) {
iowrite32(POST_STAGE_HOST_RDY, pfob->csr_va + MPU_EP_SEMAPHORE);
/* Wait for POST to complete */
s = be_wait_for_POST_complete(pfob);
} else {
/*
* Either a timeout waiting for host ready signal or POST has
* moved ahead without requiring a host ready signal.
* Might as well give POST a chance to complete
* (or timeout again).
*/
s = be_wait_for_POST_complete(pfob);
}
return s;
}
/*
*-------------------------------------------------------------------
* Function: be_pci_soft_reset
* This function is called to issue a BladeEngine soft reset.
* Callers should acquire the soft reset semaphore before calling this
* function. Additionaly, callers should ensure they cannot be pre-empted
* while the routine executes. Upon completion of this routine, callers
* should release the reset semaphore. This routine implicitly waits
* for BladeEngine POST to complete.
* pfob -
* return status - BE_SUCCESS (0) on success. Negative error code on failure.
*-------------------------------------------------------------------
*/
int be_pci_soft_reset(struct be_function_object *pfob)
{
struct PCICFG_SOFT_RESET_CSR_AMAP soft_reset;
struct PCICFG_ONLINE0_CSR_AMAP pciOnline0;
struct PCICFG_ONLINE1_CSR_AMAP pciOnline1;
struct EP_CONTROL_CSR_AMAP epControlCsr;
int status = BE_SUCCESS;
u32 i, soft_reset_bit;
TRACE(DL_NOTE, "PCI reset...");
/* Issue soft reset #1 to get BladeEngine into a known state. */
soft_reset.dw[0] = PCICFG0_READ(pfob, soft_reset);
AMAP_SET_BITS_PTR(PCICFG_SOFT_RESET_CSR, softreset, soft_reset.dw, 1);
PCICFG0_WRITE(pfob, host_timer_int_ctrl, soft_reset.dw[0]);
/*
* wait til soft reset is deasserted - hardware
* deasserts after some time.
*/
i = 0;
do {
udelay(50);
soft_reset.dw[0] = PCICFG0_READ(pfob, soft_reset);
soft_reset_bit = AMAP_GET_BITS_PTR(PCICFG_SOFT_RESET_CSR,
softreset, soft_reset.dw);
} while (soft_reset_bit && (i++ < 1024));
if (soft_reset_bit != 0) {
TRACE(DL_ERR, "Soft-reset #1 did not deassert as expected.");
status = BE_NOT_OK;
goto Error_label;
}
/* Mask everything */
PCICFG0_WRITE(pfob, ue_status_low_mask, 0xFFFFFFFF);
PCICFG0_WRITE(pfob, ue_status_hi_mask, 0xFFFFFFFF);
/*
* Set everything offline except MPU IRAM (it is offline with
* the soft-reset, but soft-reset does not reset the PCICFG registers!)
*/
pciOnline0.dw[0] = 0;
pciOnline1.dw[0] = 0;
AMAP_SET_BITS_PTR(PCICFG_ONLINE1_CSR, mpu_iram_online,
pciOnline1.dw, 1);
PCICFG0_WRITE(pfob, online0, pciOnline0.dw[0]);
PCICFG0_WRITE(pfob, online1, pciOnline1.dw[0]);
udelay(20000);
/* Issue soft reset #2. */
AMAP_SET_BITS_PTR(PCICFG_SOFT_RESET_CSR, softreset, soft_reset.dw, 1);
PCICFG0_WRITE(pfob, host_timer_int_ctrl, soft_reset.dw[0]);
/*
* wait til soft reset is deasserted - hardware
* deasserts after some time.
*/
i = 0;
do {
udelay(50);
soft_reset.dw[0] = PCICFG0_READ(pfob, soft_reset);
soft_reset_bit = AMAP_GET_BITS_PTR(PCICFG_SOFT_RESET_CSR,
softreset, soft_reset.dw);
} while (soft_reset_bit && (i++ < 1024));
if (soft_reset_bit != 0) {
TRACE(DL_ERR, "Soft-reset #1 did not deassert as expected.");
status = BE_NOT_OK;
goto Error_label;
}
udelay(20000);
/* Take MPU out of reset. */
epControlCsr.dw[0] = ioread32(pfob->csr_va + MPU_EP_CONTROL);
AMAP_SET_BITS_PTR(EP_CONTROL_CSR, CPU_reset, &epControlCsr, 0);
iowrite32((u32)epControlCsr.dw[0], pfob->csr_va + MPU_EP_CONTROL);
/* Kickoff BE POST and wait for completion */
status = be_kickoff_and_wait_for_POST(pfob);
Error_label:
return status;
}
/*
*-------------------------------------------------------------------
* Function: be_pci_reset_required
* This private function is called to detect if a host entity is
* required to issue a PCI soft reset and subsequently drive
* BladeEngine POST. Scenarios where this is required:
* 1) BIOS-less configuration
* 2) Hot-swap/plug/power-on
* pfob -
* return true if a reset is required, false otherwise
*-------------------------------------------------------------------
*/
static bool be_pci_reset_required(struct be_function_object *pfob)
{
struct MGMT_HBA_POST_STATUS_STRUCT_AMAP status;
bool do_reset = false;
u32 post_error, post_stage;
/*
* Read the POST status register
*/
status.dw[0] = ioread32(pfob->csr_va + MPU_EP_SEMAPHORE);
post_error = AMAP_GET_BITS_PTR(MGMT_HBA_POST_STATUS_STRUCT, error,
&status);
post_stage = AMAP_GET_BITS_PTR(MGMT_HBA_POST_STATUS_STRUCT, stage,
&status);
if (post_stage <= POST_STAGE_AWAITING_HOST_RDY) {
/*
* If BladeEngine is waiting for host ready indication,
* we want to do a PCI reset.
*/
do_reset = true;
}
return do_reset;
}
/*
*-------------------------------------------------------------------
* Function: be_drive_POST
* This function is called to drive BladeEngine POST. The
* caller should ensure they cannot be pre-empted while this routine executes.
* pfob -
* return status - BE_SUCCESS (0) on success. Negative error code on failure.
*-------------------------------------------------------------------
*/
int be_drive_POST(struct be_function_object *pfob)
{
int status;
if (false != be_pci_reset_required(pfob)) {
/* PCI reset is needed (implicitly starts and waits for POST) */
status = be_pci_soft_reset(pfob);
} else {
/* No PCI reset is needed, start POST */
status = be_kickoff_and_wait_for_POST(pfob);
}
return status;
}