a9acfa42bf
Signed-off-by: Sathya Perla <sathyap@serverengines.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1365 lines
39 KiB
C
1365 lines
39 KiB
C
/*
|
|
* 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;
|
|
}
|