| /* | |
| * 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; | |
| } | |