| /* |
| * 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 "hwlib.h" |
| #include "bestatus.h" |
| |
| |
| int |
| be_function_internal_query_firmware_config(struct be_function_object *pfob, |
| struct BE_FIRMWARE_CONFIG *config) |
| { |
| struct FWCMD_COMMON_FIRMWARE_CONFIG *fwcmd = NULL; |
| struct MCC_WRB_AMAP *wrb = NULL; |
| int status = 0; |
| unsigned long irql; |
| struct be_mcc_wrb_response_copy rc; |
| |
| spin_lock_irqsave(&pfob->post_lock, irql); |
| |
| wrb = be_function_peek_mcc_wrb(pfob); |
| if (!wrb) { |
| TRACE(DL_ERR, "MCC wrb peek failed."); |
| status = BE_STATUS_NO_MCC_WRB; |
| goto error; |
| } |
| /* Prepares an embedded fwcmd, including request/response sizes. */ |
| fwcmd = BE_PREPARE_EMBEDDED_FWCMD(pfob, wrb, COMMON_FIRMWARE_CONFIG); |
| |
| rc.length = FIELD_SIZEOF(struct FWCMD_COMMON_FIRMWARE_CONFIG, |
| params.response); |
| rc.fwcmd_offset = offsetof(struct FWCMD_COMMON_FIRMWARE_CONFIG, |
| params.response); |
| rc.va = config; |
| |
| /* Post the f/w command */ |
| status = be_function_post_mcc_wrb(pfob, wrb, NULL, NULL, |
| NULL, NULL, NULL, fwcmd, &rc); |
| 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 allocates and initializes a function object based on the information |
| provided by upper layer drivers. |
| |
| Returns BE_SUCCESS on success and an appropriate int on failure. |
| |
| A function object represents a single BladeEngine (logical) PCI function. |
| That is a function object either represents |
| the networking side of BladeEngine or the iSCSI side of BladeEngine. |
| |
| This routine will also detect and create an appropriate PD object for the |
| PCI function as needed. |
| */ |
| int |
| be_function_object_create(u8 __iomem *csr_va, u8 __iomem *db_va, |
| u8 __iomem *pci_va, u32 function_type, |
| struct ring_desc *mailbox, struct be_function_object *pfob) |
| { |
| int status; |
| |
| ASSERT(pfob); /* not a magic assert */ |
| ASSERT(function_type <= 2); |
| |
| TRACE(DL_INFO, "Create function object. type:%s object:0x%p", |
| (function_type == BE_FUNCTION_TYPE_ISCSI ? "iSCSI" : |
| (function_type == BE_FUNCTION_TYPE_NETWORK ? "Network" : |
| "Arm")), pfob); |
| |
| memset(pfob, 0, sizeof(*pfob)); |
| |
| pfob->type = function_type; |
| pfob->csr_va = csr_va; |
| pfob->db_va = db_va; |
| pfob->pci_va = pci_va; |
| |
| spin_lock_init(&pfob->cq_lock); |
| spin_lock_init(&pfob->post_lock); |
| spin_lock_init(&pfob->mcc_context_lock); |
| |
| |
| pfob->pci_function_number = 1; |
| |
| |
| pfob->emulate = false; |
| TRACE(DL_NOTE, "Non-emulation mode"); |
| status = be_drive_POST(pfob); |
| if (status != BE_SUCCESS) { |
| TRACE(DL_ERR, "BladeEngine POST failed."); |
| goto error; |
| } |
| |
| /* Initialize the mailbox */ |
| status = be_mpu_init_mailbox(pfob, mailbox); |
| if (status != BE_SUCCESS) { |
| TRACE(DL_ERR, "Failed to initialize mailbox."); |
| goto error; |
| } |
| /* |
| * Cache the firmware config for ASSERTs in hwclib and later |
| * driver queries. |
| */ |
| status = be_function_internal_query_firmware_config(pfob, |
| &pfob->fw_config); |
| if (status != BE_SUCCESS) { |
| TRACE(DL_ERR, "Failed to query firmware config."); |
| goto error; |
| } |
| |
| error: |
| if (status != BE_SUCCESS) { |
| /* No cleanup necessary */ |
| TRACE(DL_ERR, "Failed to create function."); |
| memset(pfob, 0, sizeof(*pfob)); |
| } |
| return status; |
| } |
| |
| /* |
| This routine drops the reference count on a given function object. Once |
| the reference count falls to zero, the function object is destroyed and all |
| resources held are freed. |
| |
| FunctionObject - The function object to drop the reference to. |
| */ |
| int be_function_object_destroy(struct be_function_object *pfob) |
| { |
| TRACE(DL_INFO, "Destroy pfob. Object:0x%p", |
| pfob); |
| |
| |
| ASSERT(pfob->mcc == NULL); |
| |
| return BE_SUCCESS; |
| } |
| |
| int be_function_cleanup(struct be_function_object *pfob) |
| { |
| int status = 0; |
| u32 isr; |
| u32 host_intr; |
| struct PCICFG_HOST_TIMER_INT_CTRL_CSR_AMAP ctrl; |
| |
| |
| if (pfob->type == BE_FUNCTION_TYPE_NETWORK) { |
| status = be_rxf_multicast_config(pfob, false, 0, |
| NULL, NULL, NULL, NULL); |
| ASSERT(status == BE_SUCCESS); |
| } |
| /* VLAN */ |
| status = be_rxf_vlan_config(pfob, false, 0, NULL, NULL, NULL, NULL); |
| ASSERT(status == BE_SUCCESS); |
| /* |
| * MCC Queue -- Switches to mailbox mode. May want to destroy |
| * all but the MCC CQ before this call if polling CQ is much better |
| * performance than polling mailbox register. |
| */ |
| if (pfob->mcc) |
| status = be_mcc_ring_destroy(pfob->mcc); |
| /* |
| * If interrupts are disabled, clear any CEV interrupt assertions that |
| * fired after we stopped processing EQs. |
| */ |
| ctrl.dw[0] = PCICFG1_READ(pfob, host_timer_int_ctrl); |
| host_intr = AMAP_GET_BITS_PTR(PCICFG_HOST_TIMER_INT_CTRL_CSR, |
| hostintr, ctrl.dw); |
| if (!host_intr) |
| if (pfob->type == BE_FUNCTION_TYPE_NETWORK) |
| isr = CSR_READ(pfob, cev.isr1); |
| else |
| isr = CSR_READ(pfob, cev.isr0); |
| else |
| /* This should never happen... */ |
| TRACE(DL_ERR, "function_cleanup called with interrupt enabled"); |
| /* Function object destroy */ |
| status = be_function_object_destroy(pfob); |
| ASSERT(status == BE_SUCCESS); |
| |
| return status; |
| } |
| |
| |
| void * |
| be_function_prepare_embedded_fwcmd(struct be_function_object *pfob, |
| struct MCC_WRB_AMAP *wrb, u32 payld_len, u32 request_length, |
| u32 response_length, u32 opcode, u32 subsystem) |
| { |
| struct FWCMD_REQUEST_HEADER *header = NULL; |
| u32 n; |
| |
| ASSERT(wrb); |
| |
| n = offsetof(struct BE_MCC_WRB_AMAP, payload)/8; |
| AMAP_SET_BITS_PTR(MCC_WRB, embedded, wrb, 1); |
| AMAP_SET_BITS_PTR(MCC_WRB, payload_length, wrb, min(payld_len, n)); |
| header = (struct FWCMD_REQUEST_HEADER *)((u8 *)wrb + n); |
| |
| header->timeout = 0; |
| header->domain = 0; |
| header->request_length = max(request_length, response_length); |
| header->opcode = opcode; |
| header->subsystem = subsystem; |
| |
| return header; |
| } |
| |
| void * |
| be_function_prepare_nonembedded_fwcmd(struct be_function_object *pfob, |
| struct MCC_WRB_AMAP *wrb, |
| void *fwcmd_va, u64 fwcmd_pa, |
| u32 payld_len, |
| u32 request_length, |
| u32 response_length, |
| u32 opcode, u32 subsystem) |
| { |
| struct FWCMD_REQUEST_HEADER *header = NULL; |
| u32 n; |
| struct MCC_WRB_PAYLOAD_AMAP *plp; |
| |
| ASSERT(wrb); |
| ASSERT(fwcmd_va); |
| |
| header = (struct FWCMD_REQUEST_HEADER *) fwcmd_va; |
| |
| AMAP_SET_BITS_PTR(MCC_WRB, embedded, wrb, 0); |
| AMAP_SET_BITS_PTR(MCC_WRB, payload_length, wrb, payld_len); |
| |
| /* |
| * Assume one fragment. The caller may override the SGL by |
| * rewriting the 0th length and adding more entries. They |
| * will also need to update the sge_count. |
| */ |
| AMAP_SET_BITS_PTR(MCC_WRB, sge_count, wrb, 1); |
| |
| n = offsetof(struct BE_MCC_WRB_AMAP, payload)/8; |
| plp = (struct MCC_WRB_PAYLOAD_AMAP *)((u8 *)wrb + n); |
| AMAP_SET_BITS_PTR(MCC_WRB_PAYLOAD, sgl[0].length, plp, payld_len); |
| AMAP_SET_BITS_PTR(MCC_WRB_PAYLOAD, sgl[0].pa_lo, plp, (u32)fwcmd_pa); |
| AMAP_SET_BITS_PTR(MCC_WRB_PAYLOAD, sgl[0].pa_hi, plp, |
| upper_32_bits(fwcmd_pa)); |
| |
| header->timeout = 0; |
| header->domain = 0; |
| header->request_length = max(request_length, response_length); |
| header->opcode = opcode; |
| header->subsystem = subsystem; |
| |
| return header; |
| } |
| |
| struct MCC_WRB_AMAP * |
| be_function_peek_mcc_wrb(struct be_function_object *pfob) |
| { |
| struct MCC_WRB_AMAP *wrb = NULL; |
| u32 offset; |
| |
| if (pfob->mcc) |
| wrb = _be_mpu_peek_ring_wrb(pfob->mcc, false); |
| else { |
| offset = offsetof(struct BE_MCC_MAILBOX_AMAP, wrb)/8; |
| wrb = (struct MCC_WRB_AMAP *) ((u8 *) pfob->mailbox.va + |
| offset); |
| } |
| |
| if (wrb) |
| memset(wrb, 0, sizeof(struct MCC_WRB_AMAP)); |
| |
| return wrb; |
| } |
| |
| #if defined(BE_DEBUG) |
| void be_function_debug_print_wrb(struct be_function_object *pfob, |
| struct MCC_WRB_AMAP *wrb, void *optional_fwcmd_va, |
| struct be_mcc_wrb_context *wrb_context) |
| { |
| |
| struct FWCMD_REQUEST_HEADER *header = NULL; |
| u8 embedded; |
| u32 n; |
| |
| embedded = AMAP_GET_BITS_PTR(MCC_WRB, embedded, wrb); |
| |
| if (embedded) { |
| n = offsetof(struct BE_MCC_WRB_AMAP, payload)/8; |
| header = (struct FWCMD_REQUEST_HEADER *)((u8 *)wrb + n); |
| } else { |
| header = (struct FWCMD_REQUEST_HEADER *) optional_fwcmd_va; |
| } |
| |
| /* Save the completed count before posting for a debug assert. */ |
| |
| if (header) { |
| wrb_context->opcode = header->opcode; |
| wrb_context->subsystem = header->subsystem; |
| |
| } else { |
| wrb_context->opcode = 0; |
| wrb_context->subsystem = 0; |
| } |
| } |
| #else |
| #define be_function_debug_print_wrb(a_, b_, c_, d_) |
| #endif |
| |
| int |
| be_function_post_mcc_wrb(struct be_function_object *pfob, |
| struct MCC_WRB_AMAP *wrb, |
| struct be_generic_q_ctxt *q_ctxt, |
| mcc_wrb_cqe_callback cb, void *cb_context, |
| mcc_wrb_cqe_callback internal_cb, |
| void *internal_cb_context, void *optional_fwcmd_va, |
| struct be_mcc_wrb_response_copy *rc) |
| { |
| int status; |
| struct be_mcc_wrb_context *wrb_context = NULL; |
| u64 *p; |
| |
| if (q_ctxt) { |
| /* Initialize context. */ |
| q_ctxt->context.internal_cb = internal_cb; |
| q_ctxt->context.internal_cb_context = internal_cb_context; |
| q_ctxt->context.cb = cb; |
| q_ctxt->context.cb_context = cb_context; |
| if (rc) { |
| q_ctxt->context.copy.length = rc->length; |
| q_ctxt->context.copy.fwcmd_offset = rc->fwcmd_offset; |
| q_ctxt->context.copy.va = rc->va; |
| } else |
| q_ctxt->context.copy.length = 0; |
| |
| q_ctxt->context.optional_fwcmd_va = optional_fwcmd_va; |
| |
| /* Queue this request */ |
| status = be_function_queue_mcc_wrb(pfob, q_ctxt); |
| |
| goto Error; |
| } |
| /* |
| * Allocate a WRB context struct to hold the callback pointers, |
| * status, etc. This is required if commands complete out of order. |
| */ |
| wrb_context = _be_mcc_allocate_wrb_context(pfob); |
| if (!wrb_context) { |
| TRACE(DL_WARN, "Failed to allocate MCC WRB context."); |
| status = BE_STATUS_SYSTEM_RESOURCES; |
| goto Error; |
| } |
| /* Initialize context. */ |
| memset(wrb_context, 0, sizeof(*wrb_context)); |
| wrb_context->internal_cb = internal_cb; |
| wrb_context->internal_cb_context = internal_cb_context; |
| wrb_context->cb = cb; |
| wrb_context->cb_context = cb_context; |
| if (rc) { |
| wrb_context->copy.length = rc->length; |
| wrb_context->copy.fwcmd_offset = rc->fwcmd_offset; |
| wrb_context->copy.va = rc->va; |
| } else |
| wrb_context->copy.length = 0; |
| wrb_context->wrb = wrb; |
| |
| /* |
| * Copy the context pointer into the WRB opaque tag field. |
| * Verify assumption of 64-bit tag with a compile time assert. |
| */ |
| p = (u64 *) ((u8 *)wrb + offsetof(struct BE_MCC_WRB_AMAP, tag)/8); |
| *p = (u64)(size_t)wrb_context; |
| |
| /* Print info about this FWCMD for debug builds. */ |
| be_function_debug_print_wrb(pfob, wrb, optional_fwcmd_va, wrb_context); |
| |
| /* |
| * issue the WRB to the MPU as appropriate |
| */ |
| if (pfob->mcc) { |
| /* |
| * we're in WRB mode, pass to the mcc layer |
| */ |
| status = _be_mpu_post_wrb_ring(pfob->mcc, wrb, wrb_context); |
| } else { |
| /* |
| * we're in mailbox mode |
| */ |
| status = _be_mpu_post_wrb_mailbox(pfob, wrb, wrb_context); |
| |
| /* mailbox mode always completes synchronously */ |
| ASSERT(status != BE_STATUS_PENDING); |
| } |
| |
| Error: |
| |
| return status; |
| } |
| |
| int |
| be_function_ring_destroy(struct be_function_object *pfob, |
| u32 id, u32 ring_type, mcc_wrb_cqe_callback cb, |
| void *cb_context, mcc_wrb_cqe_callback internal_cb, |
| void *internal_cb_context) |
| { |
| |
| struct FWCMD_COMMON_RING_DESTROY *fwcmd = NULL; |
| struct MCC_WRB_AMAP *wrb = NULL; |
| int status = 0; |
| unsigned long irql; |
| |
| spin_lock_irqsave(&pfob->post_lock, irql); |
| |
| TRACE(DL_INFO, "Destroy ring id:%d type:%d", id, ring_type); |
| |
| wrb = be_function_peek_mcc_wrb(pfob); |
| if (!wrb) { |
| ASSERT(wrb); |
| TRACE(DL_ERR, "No free MCC WRBs in destroy ring."); |
| status = BE_STATUS_NO_MCC_WRB; |
| goto Error; |
| } |
| /* Prepares an embedded fwcmd, including request/response sizes. */ |
| fwcmd = BE_PREPARE_EMBEDDED_FWCMD(pfob, wrb, COMMON_RING_DESTROY); |
| |
| fwcmd->params.request.id = id; |
| fwcmd->params.request.ring_type = ring_type; |
| |
| /* Post the f/w command */ |
| status = be_function_post_mcc_wrb(pfob, wrb, NULL, cb, cb_context, |
| internal_cb, internal_cb_context, fwcmd, NULL); |
| if (status != BE_SUCCESS && status != BE_PENDING) { |
| TRACE(DL_ERR, "Ring destroy fwcmd failed. id:%d ring_type:%d", |
| id, ring_type); |
| goto Error; |
| } |
| |
| 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; |
| } |
| |
| void |
| be_rd_to_pa_list(struct ring_desc *rd, struct PHYS_ADDR *pa_list, u32 max_num) |
| { |
| u32 num_pages = PAGES_SPANNED(rd->va, rd->length); |
| u32 i = 0; |
| u64 pa = rd->pa; |
| __le64 lepa; |
| |
| ASSERT(pa_list); |
| ASSERT(pa); |
| |
| for (i = 0; i < min(num_pages, max_num); i++) { |
| lepa = cpu_to_le64(pa); |
| pa_list[i].lo = (u32)lepa; |
| pa_list[i].hi = upper_32_bits(lepa); |
| pa += PAGE_SIZE; |
| } |
| } |
| |
| |
| |
| /*----------------------------------------------------------------------------- |
| * Function: be_function_get_fw_version |
| * Retrieves the firmware version on the adpater. If the callback is |
| * NULL this call executes synchronously. If the callback is not NULL, |
| * the returned status will be BE_PENDING if the command was issued |
| * successfully. |
| * pfob - |
| * fwv - Pointer to response buffer if callback is NULL. |
| * cb - Callback function invoked when the FWCMD completes. |
| * cb_context - Passed to the callback function. |
| * return pend_status - BE_SUCCESS (0) on success. |
| * BE_PENDING (postive value) if the FWCMD |
| * completion is pending. Negative error code on failure. |
| *--------------------------------------------------------------------------- |
| */ |
| int |
| be_function_get_fw_version(struct be_function_object *pfob, |
| struct FWCMD_COMMON_GET_FW_VERSION_RESPONSE_PAYLOAD *fwv, |
| mcc_wrb_cqe_callback cb, void *cb_context) |
| { |
| int status = BE_SUCCESS; |
| struct MCC_WRB_AMAP *wrb = NULL; |
| struct FWCMD_COMMON_GET_FW_VERSION *fwcmd = NULL; |
| unsigned long irql; |
| struct be_mcc_wrb_response_copy rc; |
| |
| spin_lock_irqsave(&pfob->post_lock, irql); |
| |
| wrb = be_function_peek_mcc_wrb(pfob); |
| if (!wrb) { |
| TRACE(DL_ERR, "MCC wrb peek failed."); |
| status = BE_STATUS_NO_MCC_WRB; |
| goto Error; |
| } |
| |
| if (!cb && !fwv) { |
| TRACE(DL_ERR, "callback and response buffer NULL!"); |
| status = BE_NOT_OK; |
| goto Error; |
| } |
| /* Prepares an embedded fwcmd, including request/response sizes. */ |
| fwcmd = BE_PREPARE_EMBEDDED_FWCMD(pfob, wrb, COMMON_GET_FW_VERSION); |
| |
| rc.length = FIELD_SIZEOF(struct FWCMD_COMMON_GET_FW_VERSION, |
| params.response); |
| rc.fwcmd_offset = offsetof(struct FWCMD_COMMON_GET_FW_VERSION, |
| params.response); |
| rc.va = fwv; |
| |
| /* Post the f/w command */ |
| status = be_function_post_mcc_wrb(pfob, wrb, NULL, cb, |
| cb_context, NULL, NULL, fwcmd, &rc); |
| |
| 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; |
| } |
| |
| int |
| be_function_queue_mcc_wrb(struct be_function_object *pfob, |
| struct be_generic_q_ctxt *q_ctxt) |
| { |
| int status; |
| |
| ASSERT(q_ctxt); |
| |
| /* |
| * issue the WRB to the MPU as appropriate |
| */ |
| if (pfob->mcc) { |
| |
| /* We're in ring mode. Queue this item. */ |
| pfob->mcc->backlog_length++; |
| list_add_tail(&q_ctxt->context.list, &pfob->mcc->backlog); |
| status = BE_PENDING; |
| } else { |
| status = BE_NOT_OK; |
| } |
| return status; |
| } |
| |