| /* |
| * Copyright (c) 2013-2014,2016-2017 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| |
| #include <osdep.h> |
| #include "a_types.h" |
| #include <athdefs.h> |
| #include "osapi_linux.h" |
| #include "hif_msg_based.h" |
| #include "if_pci.h" |
| #include "copy_engine_api.h" |
| #include "copy_engine_internal.h" |
| #include "adf_os_lock.h" |
| #include "hif_pci.h" |
| #include "regtable.h" |
| #include <vos_getBin.h> |
| #include "epping_main.h" |
| #include "adf_trace.h" |
| #include "vos_api.h" |
| |
| #define CE_POLL_TIMEOUT 10 /* ms */ |
| |
| static int war1_allow_sleep; |
| extern int hif_pci_war1; |
| |
| /* |
| * Support for Copy Engine hardware, which is mainly used for |
| * communication between Host and Target over a PCIe interconnect. |
| */ |
| |
| /** |
| * enum hif_ce_event_type - HIF copy engine event type |
| * @HIF_RX_DESC_POST: event recorded before updating write index of RX ring. |
| * @HIF_RX_DESC_COMPLETION: event recorded before updating sw index of RX ring. |
| * @HIF_TX_GATHER_DESC_POST: post gather desc. (no write index update) |
| * @HIF_TX_DESC_POST: event recorded before updating write index of TX ring. |
| * @HIF_TX_DESC_COMPLETION: event recorded before updating sw index of TX ring. |
| */ |
| enum hif_ce_event_type { |
| HIF_RX_DESC_POST, |
| HIF_RX_DESC_COMPLETION, |
| HIF_TX_GATHER_DESC_POST, |
| HIF_TX_DESC_POST, |
| HIF_TX_DESC_COMPLETION, |
| }; |
| |
| static struct hif_pci_softc *hif_sc = NULL; |
| /** |
| * target_reset_work_handler() - Work queue handler to reset target |
| * @sc: work queue handle |
| */ |
| void target_reset_work_handler(struct work_struct *sc) |
| { |
| if (hif_sc) |
| vos_device_crashed(hif_sc->dev); |
| } |
| |
| static DECLARE_WORK(target_reset_work, target_reset_work_handler); |
| |
| /** |
| * ce_target_reset() - Trigger SSR |
| * @sc: hif layer handle |
| * |
| * Once hw error is hit, SSR would be triggered. |
| */ |
| void ce_target_reset(struct hif_pci_softc *sc) |
| { |
| hif_sc = sc; |
| adf_os_warn(1); |
| adf_os_print("Trigger SSR.\n"); |
| schedule_work(&target_reset_work); |
| } |
| |
| /** |
| * ce_idx_invalid() - check whether a CE index is valid |
| * @ring: ring handle |
| * @idx: CE idx |
| */ |
| static bool ce_idx_invalid(struct CE_ring_state *ring, unsigned int idx) |
| { |
| if (ring->nentries <= idx) { |
| adf_os_print("hw index %d is not correct.\n", idx); |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| } |
| |
| #ifdef CONFIG_SLUB_DEBUG_ON |
| |
| /** |
| * struct hif_ce_event - structure for detailing a ce event |
| * @type: what the event was |
| * @time: when it happened |
| * @descriptor: descriptor enqueued or dequeued |
| * @memory: virtual address that was used |
| * @index: location of the descriptor in the ce ring; |
| */ |
| struct hif_ce_desc_event { |
| uint16_t index; |
| enum hif_ce_event_type type; |
| uint64_t time; |
| CE_desc descriptor; |
| void* memory; |
| }; |
| |
| /* max history to record per copy engine */ |
| #define HIF_CE_HISTORY_MAX 512 |
| adf_os_atomic_t hif_ce_desc_history_index[CE_COUNT_MAX]; |
| struct hif_ce_desc_event hif_ce_desc_history[CE_COUNT_MAX][HIF_CE_HISTORY_MAX]; |
| static void CE_init_ce_desc_event_log(int ce_id, int size); |
| |
| /** |
| * get_next_record_index() - get the next record index |
| * @table_index: atomic index variable to increment |
| * @array_size: array size of the circular buffer |
| * |
| * Increment the atomic index and reserve the value. |
| * Takes care of buffer wrap. |
| * Guaranteed to be thread safe as long as fewer than array_size contexts |
| * try to access the array. If there are more than array_size contexts |
| * trying to access the array, full locking of the recording process would |
| * be needed to have sane logging. |
| */ |
| int get_next_record_index(adf_os_atomic_t *table_index, int array_size) |
| { |
| int record_index = adf_os_atomic_inc_return(table_index); |
| if (record_index == array_size) |
| adf_os_atomic_sub(array_size, table_index); |
| |
| while (record_index >= array_size) |
| record_index -= array_size; |
| return record_index; |
| } |
| |
| /** |
| * hif_record_ce_desc_event() - record ce descriptor events |
| * @ce_id: which ce is the event occuring on |
| * @type: what happened |
| * @descriptor: pointer to the descriptor posted/completed |
| * @memory: virtual address of buffer related to the descriptor |
| * @index: index that the descriptor was/will be at. |
| */ |
| static void hif_record_ce_desc_event(int ce_id, enum hif_ce_event_type type, |
| CE_desc *descriptor, void *memory, int index) |
| { |
| int record_index = get_next_record_index( |
| &hif_ce_desc_history_index[ce_id], HIF_CE_HISTORY_MAX); |
| |
| struct hif_ce_desc_event *event = |
| &hif_ce_desc_history[ce_id][record_index]; |
| event->type = type; |
| event->time = adf_get_boottime(); |
| event->descriptor = *descriptor; |
| event->memory = memory; |
| event->index = index; |
| } |
| |
| /** |
| * CE_init_ce_desc_event_log() - initialize the ce event log |
| * @ce_id: copy engine id for which we are initializing the log |
| * @size: size of array to dedicate |
| * |
| * Currently the passed size is ignored in favor of a precompiled value. |
| */ |
| static void CE_init_ce_desc_event_log(int ce_id, int size) |
| { |
| adf_os_atomic_init(&hif_ce_desc_history_index[ce_id]); |
| } |
| #else |
| static inline void hif_record_ce_desc_event( |
| int ce_id, enum hif_ce_event_type type, |
| CE_desc *descriptor, void* memory, |
| int index) |
| { |
| } |
| |
| static inline void CE_init_ce_desc_event_log(int ce_id, int size) |
| { |
| } |
| #endif |
| |
| /* |
| * A single CopyEngine (CE) comprises two "rings": |
| * a source ring |
| * a destination ring |
| * |
| * Each ring consists of a number of descriptors which specify |
| * an address, length, and meta-data. |
| * |
| * Typically, one side of the PCIe interconnect (Host or Target) |
| * controls one ring and the other side controls the other ring. |
| * The source side chooses when to initiate a transfer and it |
| * chooses what to send (buffer address, length). The destination |
| * side keeps a supply of "anonymous receive buffers" available and |
| * it handles incoming data as it arrives (when the destination |
| * recieves an interrupt). |
| * |
| * The sender may send a simple buffer (address/length) or it may |
| * send a small list of buffers. When a small list is sent, hardware |
| * "gathers" these and they end up in a single destination buffer |
| * with a single interrupt. |
| * |
| * There are several "contexts" managed by this layer -- more, it |
| * may seem -- than should be needed. These are provided mainly for |
| * maximum flexibility and especially to facilitate a simpler HIF |
| * implementation. There are per-CopyEngine recv, send, and watermark |
| * contexts. These are supplied by the caller when a recv, send, |
| * or watermark handler is established and they are echoed back to |
| * the caller when the respective callbacks are invoked. There is |
| * also a per-transfer context supplied by the caller when a buffer |
| * (or sendlist) is sent and when a buffer is enqueued for recv. |
| * These per-transfer contexts are echoed back to the caller when |
| * the buffer is sent/received. |
| */ |
| |
| /* |
| * Guts of CE_send, used by both CE_send and CE_sendlist_send. |
| * The caller takes responsibility for any needed locking. |
| */ |
| int |
| CE_completed_send_next_nolock(struct CE_state *CE_state, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp, |
| unsigned int *nbytesp, |
| unsigned int *transfer_idp, |
| unsigned int *sw_idx, |
| unsigned int *hw_idx); |
| |
| void WAR_CE_SRC_RING_WRITE_IDX_SET(struct hif_pci_softc *sc, |
| void __iomem *targid, |
| u32 ctrl_addr, |
| unsigned int write_index) |
| { |
| if (hif_pci_war1) { |
| void __iomem *indicator_addr; |
| |
| indicator_addr = targid + ctrl_addr + DST_WATERMARK_ADDRESS; |
| |
| if (!war1_allow_sleep && ctrl_addr == CE_BASE_ADDRESS(CDC_WAR_DATA_CE)) { |
| A_PCI_WRITE32(indicator_addr, |
| (CDC_WAR_MAGIC_STR | write_index)); |
| } else { |
| unsigned long irq_flags; |
| local_irq_save(irq_flags); |
| A_PCI_WRITE32(indicator_addr, 1); |
| |
| /* |
| * PCIE write waits for ACK in IPQ8K, there is no |
| * need to read back value. |
| */ |
| (void)A_PCI_READ32(indicator_addr); |
| (void)A_PCI_READ32(indicator_addr); /* conservative */ |
| |
| CE_SRC_RING_WRITE_IDX_SET(targid, |
| ctrl_addr, |
| write_index); |
| |
| A_PCI_WRITE32(indicator_addr, 0); |
| local_irq_restore(irq_flags); |
| } |
| } else |
| CE_SRC_RING_WRITE_IDX_SET(targid, ctrl_addr, write_index); |
| } |
| |
| int |
| CE_send_nolock(struct CE_handle *copyeng, |
| void *per_transfer_context, |
| CE_addr_t buffer, |
| unsigned int nbytes, |
| unsigned int transfer_id, |
| unsigned int flags) |
| { |
| int status; |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct CE_ring_state *src_ring = CE_state->src_ring; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| struct hif_pci_softc *sc = CE_state->sc; |
| A_target_id_t targid = TARGID(sc); |
| unsigned int nentries_mask = src_ring->nentries_mask; |
| unsigned int sw_index = src_ring->sw_index; |
| unsigned int write_index = src_ring->write_index; |
| |
| A_TARGET_ACCESS_BEGIN_RET(targid); |
| if (unlikely(CE_RING_DELTA(nentries_mask, write_index, sw_index-1) <= 0)) { |
| OL_ATH_CE_PKT_ERROR_COUNT_INCR(sc,CE_RING_DELTA_FAIL); |
| status = A_ERROR; |
| A_TARGET_ACCESS_END_RET(targid); |
| return status; |
| } |
| { |
| enum hif_ce_event_type event_type = HIF_TX_GATHER_DESC_POST; |
| struct CE_src_desc *src_ring_base = (struct CE_src_desc *)src_ring->base_addr_owner_space; |
| struct CE_src_desc *shadow_base = (struct CE_src_desc *)src_ring->shadow_base; |
| struct CE_src_desc *src_desc = CE_SRC_RING_TO_DESC(src_ring_base, write_index); |
| struct CE_src_desc *shadow_src_desc = CE_SRC_RING_TO_DESC(shadow_base, write_index); |
| |
| /* Update source descriptor */ |
| shadow_src_desc->src_ptr = buffer; |
| shadow_src_desc->meta_data = transfer_id; |
| |
| /* |
| * Set the swap bit if: |
| * typical sends on this CE are swapped (host is big-endian) and |
| * this send doesn't disable the swapping (data is not bytestream) |
| */ |
| shadow_src_desc->byte_swap = |
| (((CE_state->attr_flags & CE_ATTR_BYTE_SWAP_DATA) != 0) & |
| ((flags & CE_SEND_FLAG_SWAP_DISABLE) == 0)); |
| shadow_src_desc->gather = ((flags & CE_SEND_FLAG_GATHER) != 0); |
| shadow_src_desc->nbytes = nbytes; |
| |
| *src_desc = *shadow_src_desc; |
| |
| src_ring->per_transfer_context[write_index] = per_transfer_context; |
| |
| /* Update Source Ring Write Index */ |
| write_index = CE_RING_IDX_INCR(nentries_mask, write_index); |
| |
| |
| /* WORKAROUND */ |
| if (!shadow_src_desc->gather) { |
| event_type = HIF_TX_DESC_POST; |
| WAR_CE_SRC_RING_WRITE_IDX_SET(sc, targid, ctrl_addr, write_index); |
| } |
| |
| /* src_ring->write index hasn't been updated event though the register |
| * has allready been written to. |
| */ |
| hif_record_ce_desc_event(CE_state->id, event_type, |
| (CE_desc *) shadow_src_desc, per_transfer_context, |
| src_ring->write_index); |
| |
| src_ring->write_index = write_index; |
| status = A_OK; |
| } |
| A_TARGET_ACCESS_END_RET(targid); |
| |
| return status; |
| } |
| |
| int |
| CE_send(struct CE_handle *copyeng, |
| void *per_transfer_context, |
| CE_addr_t buffer, |
| unsigned int nbytes, |
| unsigned int transfer_id, |
| unsigned int flags) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| int status; |
| |
| |
| adf_os_spin_lock_bh(&sc->target_lock); |
| status = CE_send_nolock(copyeng, per_transfer_context, buffer, nbytes, transfer_id, flags); |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| |
| return status; |
| } |
| |
| unsigned int |
| CE_sendlist_sizeof(void) |
| { |
| return sizeof(struct CE_sendlist); |
| } |
| |
| void |
| CE_sendlist_init(struct CE_sendlist *sendlist) |
| { |
| struct CE_sendlist_s *sl = (struct CE_sendlist_s *)sendlist; |
| sl->num_items=0; |
| } |
| |
| int |
| CE_sendlist_buf_add(struct CE_sendlist *sendlist, |
| CE_addr_t buffer, |
| unsigned int nbytes, |
| u_int32_t flags) |
| { |
| struct CE_sendlist_s *sl = (struct CE_sendlist_s *)sendlist; |
| unsigned int num_items = sl->num_items; |
| struct CE_sendlist_item *item; |
| |
| if (num_items >= CE_SENDLIST_ITEMS_MAX) { |
| A_ASSERT(num_items < CE_SENDLIST_ITEMS_MAX); |
| return A_NO_RESOURCE; |
| } |
| |
| item = &sl->item[num_items]; |
| item->send_type = CE_SIMPLE_BUFFER_TYPE; |
| item->data = buffer; |
| item->u.nbytes = nbytes; |
| item->flags = flags; |
| sl->num_items = num_items+1; |
| return A_OK; |
| } |
| |
| int |
| CE_sendlist_send(struct CE_handle *copyeng, |
| void *per_transfer_context, |
| struct CE_sendlist *sendlist, |
| unsigned int transfer_id) |
| { |
| int status = -ENOMEM; |
| struct CE_sendlist_s *sl = (struct CE_sendlist_s *)sendlist; |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct CE_ring_state *src_ring = CE_state->src_ring; |
| struct hif_pci_softc *sc = CE_state->sc; |
| unsigned int nentries_mask = src_ring->nentries_mask; |
| unsigned int num_items = sl->num_items; |
| unsigned int sw_index ; |
| unsigned int write_index ; |
| |
| A_ASSERT((num_items > 0) && (num_items < src_ring->nentries)); |
| |
| adf_os_spin_lock_bh(&sc->target_lock); |
| sw_index = src_ring->sw_index; |
| write_index = src_ring->write_index; |
| |
| if (CE_RING_DELTA(nentries_mask, write_index, sw_index-1) >= num_items) { |
| struct CE_sendlist_item *item; |
| int i; |
| |
| /* handle all but the last item uniformly */ |
| for (i = 0; i < num_items-1; i++) { |
| item = &sl->item[i]; |
| /* TBDXXX: Support extensible sendlist_types? */ |
| A_ASSERT(item->send_type == CE_SIMPLE_BUFFER_TYPE); |
| status = CE_send_nolock(copyeng, CE_SENDLIST_ITEM_CTXT, |
| (CE_addr_t)item->data, item->u.nbytes, |
| transfer_id, |
| item->flags | CE_SEND_FLAG_GATHER); |
| A_ASSERT(status == A_OK); |
| } |
| /* provide valid context pointer for final item */ |
| item = &sl->item[i]; |
| /* TBDXXX: Support extensible sendlist_types? */ |
| A_ASSERT(item->send_type == CE_SIMPLE_BUFFER_TYPE); |
| status = CE_send_nolock(copyeng, per_transfer_context, |
| (CE_addr_t)item->data, item->u.nbytes, |
| transfer_id, item->flags); |
| A_ASSERT(status == A_OK); |
| NBUF_UPDATE_TX_PKT_COUNT((adf_nbuf_t)per_transfer_context, |
| NBUF_TX_PKT_CE); |
| DPTRACE(adf_dp_trace((adf_nbuf_t)per_transfer_context, |
| ADF_DP_TRACE_CE_PACKET_PTR_RECORD, |
| (uint8_t *)&(((adf_nbuf_t)per_transfer_context)->data), |
| sizeof(((adf_nbuf_t)per_transfer_context)->data), ADF_TX)); |
| } else { |
| /* |
| * Probably not worth the additional complexity to support |
| * partial sends with continuation or notification. We expect |
| * to use large rings and small sendlists. If we can't handle |
| * the entire request at once, punt it back to the caller. |
| */ |
| } |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| |
| return status; |
| } |
| |
| int |
| CE_recv_buf_enqueue(struct CE_handle *copyeng, |
| void *per_recv_context, |
| CE_addr_t buffer) |
| { |
| int status; |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct CE_ring_state *dest_ring = CE_state->dest_ring; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| struct hif_pci_softc *sc = CE_state->sc; |
| A_target_id_t targid = TARGID(sc); |
| unsigned int nentries_mask = dest_ring->nentries_mask; |
| unsigned int write_index ; |
| unsigned int sw_index; |
| int val = 0; |
| |
| adf_os_spin_lock_bh(&sc->target_lock); |
| write_index = dest_ring->write_index; |
| sw_index = dest_ring->sw_index; |
| |
| A_TARGET_ACCESS_BEGIN_RET_EXT(targid, val); |
| if (val == -1) { |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| return val; |
| } |
| |
| if (CE_RING_DELTA(nentries_mask, write_index, sw_index-1) > 0) { |
| struct CE_dest_desc *dest_ring_base = (struct CE_dest_desc *)dest_ring->base_addr_owner_space; |
| struct CE_dest_desc *dest_desc = CE_DEST_RING_TO_DESC(dest_ring_base, write_index); |
| |
| /* Update destination descriptor */ |
| dest_desc->dest_ptr = buffer; |
| dest_desc->info.nbytes = 0; /* NB: Enable CE_completed_recv_next_nolock to |
| protect against race between DRRI update and |
| desc update */ |
| |
| dest_ring->per_transfer_context[write_index] = per_recv_context; |
| |
| hif_record_ce_desc_event(CE_state->id, HIF_RX_DESC_POST, |
| (CE_desc *) dest_desc, per_recv_context, |
| write_index); |
| |
| /* Update Destination Ring Write Index */ |
| write_index = CE_RING_IDX_INCR(nentries_mask, write_index); |
| CE_DEST_RING_WRITE_IDX_SET(targid, ctrl_addr, write_index); |
| dest_ring->write_index = write_index; |
| status = A_OK; |
| } else { |
| status = A_ERROR; |
| } |
| A_TARGET_ACCESS_END_RET_EXT(targid, val); |
| if (val == -1) { |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| return val; |
| } |
| |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| |
| return status; |
| } |
| |
| void |
| CE_send_watermarks_set(struct CE_handle *copyeng, |
| unsigned int low_alert_nentries, |
| unsigned int high_alert_nentries) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| struct hif_pci_softc *sc = CE_state->sc; |
| A_target_id_t targid = TARGID(sc); |
| |
| adf_os_spin_lock(&sc->target_lock); |
| CE_SRC_RING_LOWMARK_SET(targid, ctrl_addr, low_alert_nentries); |
| CE_SRC_RING_HIGHMARK_SET(targid, ctrl_addr, high_alert_nentries); |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| void |
| CE_recv_watermarks_set(struct CE_handle *copyeng, |
| unsigned int low_alert_nentries, |
| unsigned int high_alert_nentries) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| struct hif_pci_softc *sc = CE_state->sc; |
| A_target_id_t targid = TARGID(sc); |
| |
| adf_os_spin_lock(&sc->target_lock); |
| CE_DEST_RING_LOWMARK_SET(targid, ctrl_addr, low_alert_nentries); |
| CE_DEST_RING_HIGHMARK_SET(targid, ctrl_addr, high_alert_nentries); |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| unsigned int |
| CE_send_entries_avail(struct CE_handle *copyeng) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct CE_ring_state *src_ring = CE_state->src_ring; |
| struct hif_pci_softc *sc = CE_state->sc; |
| unsigned int nentries_mask = src_ring->nentries_mask; |
| unsigned int sw_index; |
| unsigned int write_index; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| sw_index = src_ring->sw_index; |
| write_index = src_ring->write_index; |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| return CE_RING_DELTA(nentries_mask, write_index, sw_index-1); |
| } |
| |
| unsigned int |
| CE_recv_entries_avail(struct CE_handle *copyeng) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct CE_ring_state *dest_ring = CE_state->dest_ring; |
| struct hif_pci_softc *sc = CE_state->sc; |
| unsigned int nentries_mask = dest_ring->nentries_mask; |
| unsigned int sw_index; |
| unsigned int write_index; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| sw_index = dest_ring->sw_index; |
| write_index = dest_ring->write_index; |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| return CE_RING_DELTA(nentries_mask, write_index, sw_index-1); |
| } |
| |
| /* |
| * Guts of CE_send_entries_done. |
| * The caller takes responsibility for any necessary locking. |
| */ |
| unsigned int |
| CE_send_entries_done_nolock(struct hif_pci_softc *sc, struct CE_state *CE_state) |
| { |
| struct CE_ring_state *src_ring = CE_state->src_ring; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| A_target_id_t targid = TARGID(sc); |
| unsigned int nentries_mask = src_ring->nentries_mask; |
| unsigned int sw_index; |
| unsigned int read_index; |
| |
| sw_index = src_ring->sw_index; |
| read_index = CE_SRC_RING_READ_IDX_GET(targid, ctrl_addr); |
| |
| return CE_RING_DELTA(nentries_mask, sw_index, read_index); |
| } |
| |
| unsigned int |
| CE_send_entries_done(struct CE_handle *copyeng) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| unsigned int nentries; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| nentries = CE_send_entries_done_nolock(sc, CE_state); |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| return nentries; |
| } |
| |
| /* |
| * Guts of CE_recv_entries_done. |
| * The caller takes responsibility for any necessary locking. |
| */ |
| unsigned int |
| CE_recv_entries_done_nolock(struct hif_pci_softc *sc, struct CE_state *CE_state) |
| { |
| struct CE_ring_state *dest_ring = CE_state->dest_ring; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| A_target_id_t targid = TARGID(sc); |
| unsigned int nentries_mask = dest_ring->nentries_mask; |
| unsigned int sw_index; |
| unsigned int read_index; |
| |
| sw_index = dest_ring->sw_index; |
| read_index = CE_DEST_RING_READ_IDX_GET(targid, ctrl_addr); |
| |
| return CE_RING_DELTA(nentries_mask, sw_index, read_index); |
| } |
| |
| unsigned int |
| CE_recv_entries_done(struct CE_handle *copyeng) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| unsigned int nentries; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| nentries = CE_recv_entries_done_nolock(sc, CE_state); |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| return nentries; |
| } |
| |
| /* Debug support */ |
| void *ce_debug_cmplrn_context; /* completed recv next context */ |
| void *ce_debug_cnclsn_context; /* cancel send next context */ |
| void *ce_debug_rvkrn_context; /* revoke receive next context */ |
| void *ce_debug_cmplsn_context; /* completed send next context */ |
| |
| |
| /* |
| * Guts of CE_completed_recv_next. |
| * The caller takes responsibility for any necessary locking. |
| */ |
| int |
| CE_completed_recv_next_nolock(struct CE_state *CE_state, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp, |
| unsigned int *nbytesp, |
| unsigned int *transfer_idp, |
| unsigned int *flagsp) |
| { |
| int status; |
| struct CE_ring_state *dest_ring = CE_state->dest_ring; |
| unsigned int nentries_mask = dest_ring->nentries_mask; |
| unsigned int sw_index = dest_ring->sw_index; |
| |
| struct CE_dest_desc *dest_ring_base = (struct CE_dest_desc *)dest_ring->base_addr_owner_space; |
| struct CE_dest_desc *dest_desc = CE_DEST_RING_TO_DESC(dest_ring_base, sw_index); |
| int nbytes; |
| struct dest_desc_info dest_desc_info; |
| |
| /* |
| * By copying the dest_desc_info element to local memory, we could |
| * avoid extra memory read from non-cachable memory. |
| */ |
| dest_desc_info = dest_desc->info; |
| nbytes = dest_desc_info.nbytes; |
| if (nbytes == 0) { |
| /* |
| * This closes a relatively unusual race where the Host |
| * sees the updated DRRI before the update to the |
| * corresponding descriptor has completed. We treat this |
| * as a descriptor that is not yet done. |
| */ |
| status = A_ERROR; |
| goto done; |
| } |
| |
| hif_record_ce_desc_event(CE_state->id, HIF_RX_DESC_COMPLETION, |
| (CE_desc *) dest_desc, |
| dest_ring->per_transfer_context[sw_index], |
| sw_index); |
| |
| dest_desc->info.nbytes = 0; |
| |
| /* Return data from completed destination descriptor */ |
| *bufferp = (CE_addr_t)(dest_desc->dest_ptr); |
| *nbytesp = nbytes; |
| *transfer_idp = dest_desc_info.meta_data; |
| *flagsp = (dest_desc_info.byte_swap) ? CE_RECV_FLAG_SWAPPED : 0; |
| |
| if (per_CE_contextp) { |
| *per_CE_contextp = CE_state->recv_context; |
| } |
| |
| ce_debug_cmplrn_context = dest_ring->per_transfer_context[sw_index]; |
| if (per_transfer_contextp) { |
| *per_transfer_contextp = ce_debug_cmplrn_context; |
| } |
| dest_ring->per_transfer_context[sw_index] = 0; /* sanity */ |
| |
| /* Update sw_index */ |
| sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index); |
| dest_ring->sw_index = sw_index; |
| status = A_OK; |
| |
| done: |
| return status; |
| } |
| |
| int |
| CE_completed_recv_next(struct CE_handle *copyeng, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp, |
| unsigned int *nbytesp, |
| unsigned int *transfer_idp, |
| unsigned int *flagsp) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| int status; |
| |
| |
| adf_os_spin_lock_bh(&sc->target_lock); |
| status = CE_completed_recv_next_nolock(CE_state, per_CE_contextp, per_transfer_contextp, |
| bufferp, nbytesp, transfer_idp, flagsp); |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| |
| return status; |
| } |
| |
| /* NB: Modeled after CE_completed_recv_next_nolock */ |
| A_STATUS |
| CE_revoke_recv_next(struct CE_handle *copyeng, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp) |
| { |
| struct CE_state *CE_state; |
| struct CE_ring_state *dest_ring; |
| unsigned int nentries_mask; |
| unsigned int sw_index; |
| unsigned int write_index; |
| A_STATUS status; |
| struct hif_pci_softc *sc; |
| |
| CE_state = (struct CE_state *)copyeng; |
| dest_ring = CE_state->dest_ring; |
| if (!dest_ring) { |
| return A_ERROR; |
| } |
| |
| sc = CE_state->sc; |
| adf_os_spin_lock(&sc->target_lock); |
| nentries_mask = dest_ring->nentries_mask; |
| sw_index = dest_ring->sw_index; |
| write_index = dest_ring->write_index; |
| if (write_index != sw_index) { |
| struct CE_dest_desc *dest_ring_base = (struct CE_dest_desc *)dest_ring->base_addr_owner_space; |
| struct CE_dest_desc *dest_desc = CE_DEST_RING_TO_DESC(dest_ring_base, sw_index); |
| |
| /* Return data from completed destination descriptor */ |
| *bufferp = (CE_addr_t)(dest_desc->dest_ptr); |
| |
| if (per_CE_contextp) { |
| *per_CE_contextp = CE_state->recv_context; |
| } |
| |
| ce_debug_rvkrn_context = dest_ring->per_transfer_context[sw_index]; |
| if (per_transfer_contextp) { |
| *per_transfer_contextp = ce_debug_rvkrn_context; |
| } |
| dest_ring->per_transfer_context[sw_index] = 0; /* sanity */ |
| |
| /* Update sw_index */ |
| sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index); |
| dest_ring->sw_index = sw_index; |
| status = A_OK; |
| } else { |
| status = A_ERROR; |
| } |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| return status; |
| } |
| |
| /* |
| * Guts of CE_completed_send_next. |
| * The caller takes responsibility for any necessary locking. |
| */ |
| int |
| CE_completed_send_next_nolock(struct CE_state *CE_state, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp, |
| unsigned int *nbytesp, |
| unsigned int *transfer_idp, |
| unsigned int *sw_idx, |
| unsigned int *hw_idx) |
| { |
| int status = A_ERROR; |
| struct CE_ring_state *src_ring = CE_state->src_ring; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| struct hif_pci_softc *sc = CE_state->sc; |
| A_target_id_t targid = TARGID(sc); |
| unsigned int nentries_mask = src_ring->nentries_mask; |
| unsigned int sw_index = src_ring->sw_index; |
| unsigned int read_index; |
| |
| |
| if (src_ring->hw_index == sw_index) { |
| /* |
| * The SW completion index has caught up with the cached |
| * version of the HW completion index. |
| * Update the cached HW completion index to see whether |
| * the SW has really caught up to the HW, or if the cached |
| * value of the HW index has become stale. |
| */ |
| A_TARGET_ACCESS_BEGIN_RET(targid); |
| src_ring->hw_index = CE_SRC_RING_READ_IDX_GET(targid, ctrl_addr); |
| A_TARGET_ACCESS_END_RET(targid); |
| |
| if (ce_idx_invalid(src_ring, src_ring->hw_index)) |
| return status; |
| } |
| read_index = src_ring->hw_index; |
| |
| if (sw_idx) |
| *sw_idx = sw_index; |
| |
| if (hw_idx) |
| *hw_idx = read_index; |
| |
| if ((read_index != sw_index) && (read_index != 0xffffffff)) { |
| struct CE_src_desc *shadow_base = (struct CE_src_desc *)src_ring->shadow_base; |
| struct CE_src_desc *shadow_src_desc = CE_SRC_RING_TO_DESC(shadow_base, sw_index); |
| |
| hif_record_ce_desc_event(CE_state->id, HIF_TX_DESC_COMPLETION, |
| (CE_desc *) shadow_src_desc, |
| src_ring->per_transfer_context[sw_index], |
| sw_index); |
| |
| /* Return data from completed source descriptor */ |
| *bufferp = (CE_addr_t)(shadow_src_desc->src_ptr); |
| *nbytesp = shadow_src_desc->nbytes; |
| *transfer_idp = shadow_src_desc->meta_data; |
| |
| if (per_CE_contextp) { |
| *per_CE_contextp = CE_state->send_context; |
| } |
| |
| ce_debug_cmplsn_context = src_ring->per_transfer_context[sw_index]; |
| if (per_transfer_contextp) { |
| *per_transfer_contextp = ce_debug_cmplsn_context; |
| } |
| src_ring->per_transfer_context[sw_index] = 0; /* sanity */ |
| |
| /* Update sw_index */ |
| sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index); |
| src_ring->sw_index = sw_index; |
| status = A_OK; |
| } |
| |
| return status; |
| } |
| |
| /* NB: Modeled after CE_completed_send_next */ |
| A_STATUS |
| CE_cancel_send_next(struct CE_handle *copyeng, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp, |
| unsigned int *nbytesp, |
| unsigned int *transfer_idp) |
| { |
| struct CE_state *CE_state; |
| struct CE_ring_state *src_ring; |
| unsigned int nentries_mask; |
| unsigned int sw_index; |
| unsigned int write_index; |
| A_STATUS status; |
| struct hif_pci_softc *sc; |
| |
| CE_state = (struct CE_state *)copyeng; |
| src_ring = CE_state->src_ring; |
| if (!src_ring) { |
| return A_ERROR; |
| } |
| |
| sc = CE_state->sc; |
| adf_os_spin_lock(&sc->target_lock); |
| nentries_mask = src_ring->nentries_mask; |
| sw_index = src_ring->sw_index; |
| write_index = src_ring->write_index; |
| |
| if (write_index != sw_index) { |
| struct CE_src_desc *src_ring_base = (struct CE_src_desc *)src_ring->base_addr_owner_space; |
| struct CE_src_desc *src_desc = CE_SRC_RING_TO_DESC(src_ring_base, sw_index); |
| |
| /* Return data from completed source descriptor */ |
| *bufferp = (CE_addr_t)(src_desc->src_ptr); |
| *nbytesp = src_desc->nbytes; |
| *transfer_idp = src_desc->meta_data; |
| |
| if (per_CE_contextp) { |
| *per_CE_contextp = CE_state->send_context; |
| } |
| |
| ce_debug_cnclsn_context = src_ring->per_transfer_context[sw_index]; |
| if (per_transfer_contextp) { |
| *per_transfer_contextp = ce_debug_cnclsn_context; |
| } |
| src_ring->per_transfer_context[sw_index] = 0; /* sanity */ |
| |
| /* Update sw_index */ |
| sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index); |
| src_ring->sw_index = sw_index; |
| status = A_OK; |
| } else { |
| status = A_ERROR; |
| } |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| return status; |
| } |
| |
| /* Shift bits to convert IS_*_RING_*_WATERMARK_MASK to CE_WM_FLAG_*_* */ |
| #define CE_WM_SHFT 1 |
| |
| |
| int |
| CE_completed_send_next(struct CE_handle *copyeng, |
| void **per_CE_contextp, |
| void **per_transfer_contextp, |
| CE_addr_t *bufferp, |
| unsigned int *nbytesp, |
| unsigned int *transfer_idp, |
| unsigned int *sw_idx, |
| unsigned int *hw_idx) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| int status; |
| |
| |
| adf_os_spin_lock_bh(&sc->target_lock); |
| status = CE_completed_send_next_nolock(CE_state, per_CE_contextp, per_transfer_contextp, |
| bufferp, nbytesp, transfer_idp, |
| sw_idx, hw_idx); |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| |
| return status; |
| } |
| |
| |
| #ifdef ATH_11AC_TXCOMPACT |
| /* CE engine descriptor reap |
| Similar to CE_per_engine_service , Only difference is CE_per_engine_service |
| does recieve and reaping of completed descriptor , |
| This function only handles reaping of Tx complete descriptor. |
| The Function is called from threshold reap poll routine HIFSendCompleteCheck |
| So should not countain recieve functionality within it . |
| */ |
| |
| void |
| CE_per_engine_servicereap(struct hif_pci_softc *sc, unsigned int CE_id) |
| { |
| struct CE_state *CE_state = sc->CE_id_to_state[CE_id]; |
| A_target_id_t targid = TARGID(sc); |
| void *CE_context; |
| void *transfer_context; |
| CE_addr_t buf; |
| unsigned int nbytes; |
| unsigned int id; |
| unsigned int sw_idx, hw_idx; |
| |
| A_TARGET_ACCESS_BEGIN(targid); |
| |
| /* Since this function is called from both user context and |
| * tasklet context the spinlock has to lock the bottom halves. |
| * This fix assumes that ATH_11AC_TXCOMPACT flag is always |
| * enabled in TX polling mode. If this is not the case, more |
| * bottom halve spin lock changes are needed. Due to data path |
| * performance concern, after internal discussion we've decided |
| * to make minimum change, i.e., only address the issue occurred |
| * in this function. The possible negative effect of this minimum |
| * change is that, in the future, if some other function will also |
| * be opened to let the user context to use, those cases need to be |
| * addressed by change spin_lock to spin_lock_bh also. */ |
| |
| adf_os_spin_lock_bh(&sc->target_lock); |
| |
| if (CE_state->send_cb) { |
| { |
| /* Pop completed send buffers and call the registered send callback for each */ |
| while (CE_completed_send_next_nolock(CE_state, &CE_context, &transfer_context, |
| &buf, &nbytes, &id, &sw_idx, &hw_idx) == A_OK) |
| { |
| if(CE_id != CE_HTT_H2T_MSG){ |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| CE_state->send_cb((struct CE_handle *)CE_state, CE_context, transfer_context, buf, nbytes, id, |
| sw_idx, hw_idx); |
| adf_os_spin_lock_bh(&sc->target_lock); |
| }else{ |
| struct HIF_CE_pipe_info *pipe_info = (struct HIF_CE_pipe_info *)CE_context; |
| |
| adf_os_spin_lock_bh(&pipe_info->completion_freeq_lock); |
| pipe_info->num_sends_allowed++; |
| adf_os_spin_unlock_bh(&pipe_info->completion_freeq_lock); |
| } |
| } |
| } |
| } |
| |
| adf_os_spin_unlock_bh(&sc->target_lock); |
| A_TARGET_ACCESS_END(targid); |
| } |
| |
| #endif /*ATH_11AC_TXCOMPACT*/ |
| |
| /** |
| * ce_is_valid_entries() - check if ce ring delta is valid |
| * @sc: pointer to hif_pci_softc |
| * @ce_state: pointer to CE_state |
| * @ring_delta: delta packets in CE ring |
| * |
| * check if difference between hw index and read index is |
| * with in limit. |
| * |
| * Return: true if ring delta is valid. |
| */ |
| static inline bool ce_is_valid_entries(struct hif_pci_softc *sc, |
| struct CE_state *ce_state, unsigned int ring_delta) |
| { |
| bool status; |
| A_target_id_t targid = TARGID(sc); |
| unsigned int hw_index = |
| CE_DEST_RING_READ_IDX_GET(targid, ce_state->ctrl_addr); |
| |
| /* check if difference between hw index and read index is with in |
| * nentries_mask limit. |
| */ |
| if ((ring_delta < ce_state->dest_ring->nentries_mask) && |
| (hw_index != CE_HW_INDEX_LINK_DOWN)) { |
| adf_os_print("%s: spent more time during rx proceesing for CE%d, allow other CE to process Rx packet.\n", |
| __func__, ce_state->id); |
| status = true; |
| } else if (hw_index == CE_HW_INDEX_LINK_DOWN) { |
| status = false; |
| adf_os_print("%s: hw index is invalid due to link down \n", __func__); |
| } else { |
| adf_os_print("%s:Potential infinite loop detected during rx processing for CE%d\n", |
| __func__, ce_state->id); |
| ce_target_reset(sc); |
| status = false; |
| } |
| |
| adf_os_print("nentries_mask:0x%x sw read_idx:0x%x hw read_idx:0x%x ring_delta:0x%x\n", |
| ce_state->dest_ring->nentries_mask, |
| ce_state->dest_ring->sw_index, |
| CE_DEST_RING_READ_IDX_GET(targid, ce_state->ctrl_addr), |
| ring_delta); |
| |
| return status; |
| } |
| |
| /* |
| * Number of times to check for any pending tx/rx completion on |
| * a copy engine, this count should be big enough. Once we hit |
| * this threashold we'll not check for any Tx/Rx comlpetion in same |
| * interrupt handling. Note that this threashold is only used for |
| * Rx interrupt processing, this can be used tor Tx as well if we |
| * suspect any infinite loop in checking for pending Tx completion. |
| */ |
| #define CE_TXRX_COMP_CHECK_THRESHOLD 20 |
| |
| /* |
| * Guts of interrupt handler for per-engine interrupts on a particular CE. |
| * |
| * Invokes registered callbacks for recv_complete, |
| * send_complete, and watermarks. |
| */ |
| void |
| CE_per_engine_service(struct hif_pci_softc *sc, unsigned int CE_id) |
| { |
| struct CE_state *CE_state = sc->CE_id_to_state[CE_id]; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| A_target_id_t targid = TARGID(sc); |
| void *CE_context; |
| void *transfer_context; |
| CE_addr_t buf; |
| unsigned int nbytes; |
| unsigned int id; |
| unsigned int flags; |
| u_int32_t CE_int_status; |
| unsigned int more_comp_cnt = 0; |
| unsigned int more_snd_comp_cnt = 0; |
| unsigned int sw_idx, hw_idx; |
| bool is_pkt_pending = 0; |
| unsigned int ring_delta = 0; |
| |
| |
| A_TARGET_ACCESS_BEGIN(targid); |
| |
| adf_os_spin_lock(&sc->target_lock); |
| |
| /* Clear force_break flag and re-initialize receive_count to 0 */ |
| sc->receive_count = 0; |
| sc->force_break = 0; |
| more_completions: |
| if (CE_state->recv_cb) { |
| |
| /* Pop completed recv buffers and call the registered recv callback for each */ |
| while (CE_completed_recv_next_nolock(CE_state, &CE_context, &transfer_context, |
| &buf, &nbytes, &id, &flags) == A_OK) |
| { |
| adf_os_spin_unlock(&sc->target_lock); |
| CE_state->recv_cb((struct CE_handle *)CE_state, CE_context, transfer_context, |
| buf, nbytes, id, flags); |
| |
| /* |
| * EV #112693 - [Peregrine][ES1][WB342][Win8x86][Performance] BSoD_0x133 occurred in VHT80 UDP_DL |
| * Break out DPC by force if number of loops in HIF_PCI_CE_recv_data reaches MAX_NUM_OF_RECEIVES to avoid spending too long time in DPC for each interrupt handling. |
| * Schedule another DPC to avoid data loss if we had taken force-break action before |
| * Apply to Windows OS only currently, Linux/MAC os can expand to their platform if necessary |
| */ |
| |
| /* Break the receive processes by force if force_break set up */ |
| if (adf_os_unlikely(sc->force_break)) |
| { |
| adf_os_atomic_set(&CE_state->rx_pending, 1); |
| CE_ENGINE_INT_STATUS_CLEAR(targid, ctrl_addr, HOST_IS_COPY_COMPLETE_MASK); |
| A_TARGET_ACCESS_END(targid); |
| return; |
| } |
| adf_os_spin_lock(&sc->target_lock); |
| } |
| } |
| |
| /* |
| * Attention: We may experience potential infinite loop for below While Loop during Sending Stress test |
| * Resolve the same way as Receive Case (Refer to EV #112693) |
| */ |
| |
| if (CE_state->send_cb) { |
| /* Pop completed send buffers and call the registered send callback for each */ |
| |
| #ifdef ATH_11AC_TXCOMPACT |
| while (CE_completed_send_next_nolock(CE_state, &CE_context, &transfer_context, |
| &buf, &nbytes, &id, &sw_idx, &hw_idx) == A_OK){ |
| |
| if(CE_id != CE_HTT_H2T_MSG || |
| WLAN_IS_EPPING_ENABLED(vos_get_conparam())){ |
| adf_os_spin_unlock(&sc->target_lock); |
| CE_state->send_cb((struct CE_handle *)CE_state, CE_context, transfer_context, buf, nbytes, id, |
| sw_idx, hw_idx); |
| adf_os_spin_lock(&sc->target_lock); |
| }else{ |
| struct HIF_CE_pipe_info *pipe_info = (struct HIF_CE_pipe_info *)CE_context; |
| |
| adf_os_spin_lock(&pipe_info->completion_freeq_lock); |
| pipe_info->num_sends_allowed++; |
| adf_os_spin_unlock(&pipe_info->completion_freeq_lock); |
| } |
| } |
| #else /*ATH_11AC_TXCOMPACT*/ |
| while (CE_completed_send_next_nolock(CE_state, &CE_context, &transfer_context, |
| &buf, &nbytes, &id, &sw_idx, &hw_idx) == A_OK){ |
| adf_os_spin_unlock(&sc->target_lock); |
| CE_state->send_cb((struct CE_handle *)CE_state, CE_context, transfer_context, buf, nbytes, id, |
| sw_idx, hw_idx); |
| adf_os_spin_lock(&sc->target_lock); |
| } |
| #endif /*ATH_11AC_TXCOMPACT*/ |
| } |
| |
| more_watermarks: |
| if (CE_state->misc_cbs) { |
| CE_int_status = CE_ENGINE_INT_STATUS_GET(targid, ctrl_addr); |
| if (CE_int_status & CE_WATERMARK_MASK) { |
| if (CE_state->watermark_cb) { |
| |
| adf_os_spin_unlock(&sc->target_lock); |
| /* Convert HW IS bits to software flags */ |
| flags = (CE_int_status & CE_WATERMARK_MASK) >> CE_WM_SHFT; |
| |
| CE_state->watermark_cb((struct CE_handle *)CE_state, CE_state->wm_context, flags); |
| adf_os_spin_lock(&sc->target_lock); |
| } |
| } |
| } |
| |
| /* |
| * Clear the misc interrupts (watermark) that were handled above, |
| * and that will be checked again below. |
| * Clear and check for copy-complete interrupts again, just in case |
| * more copy completions happened while the misc interrupts were being |
| * handled. |
| */ |
| CE_ENGINE_INT_STATUS_CLEAR(targid, ctrl_addr, CE_WATERMARK_MASK | HOST_IS_COPY_COMPLETE_MASK); |
| |
| /* |
| * Now that per-engine interrupts are cleared, verify that |
| * no recv interrupts arrive while processing send interrupts, |
| * and no recv or send interrupts happened while processing |
| * misc interrupts.Go back and check again.Keep checking until |
| * we find no more events to process. |
| */ |
| if (CE_state->recv_cb) { |
| ring_delta = CE_recv_entries_done_nolock(sc, CE_state); |
| if(ring_delta) { |
| if (WLAN_IS_EPPING_ENABLED(vos_get_conparam()) || |
| more_comp_cnt++ < CE_TXRX_COMP_CHECK_THRESHOLD) { |
| goto more_completions; |
| } else { |
| if (ce_is_valid_entries(sc, CE_state, ring_delta)) |
| is_pkt_pending = true; |
| } |
| } |
| } |
| |
| if (CE_state->send_cb && CE_send_entries_done_nolock(sc, CE_state)) { |
| if (WLAN_IS_EPPING_ENABLED(vos_get_conparam()) || |
| more_snd_comp_cnt++ < CE_TXRX_COMP_CHECK_THRESHOLD) { |
| goto more_completions; |
| } else { |
| adf_os_print("%s:Potential infinite loop detected during send completion " |
| "nentries_mask:0x%x sw read_idx:0x%x hw read_idx:0x%x\n", |
| __func__, CE_state->src_ring->nentries_mask, |
| CE_state->src_ring->sw_index, |
| CE_SRC_RING_READ_IDX_GET(targid, CE_state->ctrl_addr)); |
| } |
| } |
| |
| |
| if (CE_state->misc_cbs) { |
| CE_int_status = CE_ENGINE_INT_STATUS_GET(targid, ctrl_addr); |
| if (CE_int_status & CE_WATERMARK_MASK) { |
| if (CE_state->watermark_cb) { |
| goto more_watermarks; |
| } |
| } |
| } |
| |
| adf_os_spin_unlock(&sc->target_lock); |
| if (is_pkt_pending) |
| adf_os_atomic_set(&CE_state->rx_pending, 1); |
| else |
| adf_os_atomic_set(&CE_state->rx_pending, 0); |
| A_TARGET_ACCESS_END(targid); |
| } |
| |
| static void |
| CE_poll_timeout(void *arg) |
| { |
| struct CE_state *CE_state = (struct CE_state *) arg; |
| if (CE_state->timer_inited) { |
| CE_per_engine_service(CE_state->sc, CE_state->id); |
| adf_os_timer_mod(&CE_state->poll_timer, CE_POLL_TIMEOUT); |
| } |
| } |
| |
| |
| /* |
| * Handler for per-engine interrupts on ALL active CEs. |
| * This is used in cases where the system is sharing a |
| * single interrput for all CEs |
| */ |
| |
| void |
| CE_per_engine_service_any(int irq, void *arg) |
| { |
| struct hif_pci_softc *sc = arg; |
| A_target_id_t targid = TARGID(sc); |
| int CE_id; |
| A_UINT32 intr_summary; |
| |
| A_TARGET_ACCESS_BEGIN(targid); |
| if (!adf_os_atomic_read(&sc->tasklet_from_intr)) { |
| for (CE_id=0; CE_id < sc->ce_count; CE_id++) { |
| struct CE_state *CE_state = sc->CE_id_to_state[CE_id]; |
| if (adf_os_atomic_read(&CE_state->rx_pending)) { |
| adf_os_atomic_set(&CE_state->rx_pending, 0); |
| CE_per_engine_service(sc, CE_id); |
| } |
| } |
| |
| A_TARGET_ACCESS_END(targid); |
| return; |
| } |
| |
| intr_summary = CE_INTERRUPT_SUMMARY(targid); |
| |
| for (CE_id=0; intr_summary && (CE_id < sc->ce_count); CE_id++) { |
| if (intr_summary & (1<<CE_id)) { |
| intr_summary &= ~(1<<CE_id); |
| } else { |
| continue; /* no intr pending on this CE */ |
| } |
| |
| CE_per_engine_service(sc, CE_id); |
| } |
| |
| A_TARGET_ACCESS_END(targid); |
| } |
| |
| /* |
| * Adjust interrupts for the copy complete handler. |
| * If it's needed for either send or recv, then unmask |
| * this interrupt; otherwise, mask it. |
| * |
| * Called with target_lock held. |
| */ |
| static void |
| CE_per_engine_handler_adjust(struct CE_state *CE_state, |
| int disable_copy_compl_intr) |
| { |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| struct hif_pci_softc *sc = CE_state->sc; |
| A_target_id_t targid = TARGID(sc); |
| CE_state->disable_copy_compl_intr = disable_copy_compl_intr; |
| |
| A_TARGET_ACCESS_BEGIN(targid); |
| if ((!disable_copy_compl_intr) && |
| (CE_state->send_cb || CE_state->recv_cb)) |
| { |
| CE_COPY_COMPLETE_INTR_ENABLE(targid, ctrl_addr); |
| } else { |
| CE_COPY_COMPLETE_INTR_DISABLE(targid, ctrl_addr); |
| } |
| |
| if (CE_state->watermark_cb) { |
| CE_WATERMARK_INTR_ENABLE(targid, ctrl_addr); |
| } else { |
| CE_WATERMARK_INTR_DISABLE(targid, ctrl_addr); |
| } |
| A_TARGET_ACCESS_END(targid); |
| |
| } |
| |
| /*Iterate the CE_state list and disable the compl interrupt if it has been registered already.*/ |
| void CE_disable_any_copy_compl_intr_nolock(struct hif_pci_softc *sc) |
| { |
| A_target_id_t targid = TARGID(sc); |
| int CE_id; |
| |
| A_TARGET_ACCESS_BEGIN(targid); |
| for (CE_id=0; CE_id < sc->ce_count; CE_id++) { |
| struct CE_state *CE_state = sc->CE_id_to_state[CE_id]; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| |
| /* if the interrupt is currently enabled, disable it */ |
| if (!CE_state->disable_copy_compl_intr && (CE_state->send_cb || CE_state->recv_cb)) { |
| CE_COPY_COMPLETE_INTR_DISABLE(targid, ctrl_addr); |
| } |
| |
| if (CE_state->watermark_cb) { |
| CE_WATERMARK_INTR_DISABLE(targid, ctrl_addr); |
| } |
| } |
| A_TARGET_ACCESS_END(targid); |
| } |
| |
| void CE_enable_any_copy_compl_intr_nolock(struct hif_pci_softc *sc) |
| { |
| A_target_id_t targid = TARGID(sc); |
| int CE_id; |
| |
| A_TARGET_ACCESS_BEGIN(targid); |
| for (CE_id=0; CE_id < sc->ce_count; CE_id++) { |
| struct CE_state *CE_state = sc->CE_id_to_state[CE_id]; |
| u_int32_t ctrl_addr = CE_state->ctrl_addr; |
| |
| /* |
| * If the CE is supposed to have copy complete interrupts enabled |
| * (i.e. there a callback registered, and the "disable" flag is not set), |
| * then re-enable the interrupt. |
| */ |
| if (!CE_state->disable_copy_compl_intr && (CE_state->send_cb || CE_state->recv_cb)) { |
| CE_COPY_COMPLETE_INTR_ENABLE(targid, ctrl_addr); |
| } |
| |
| if (CE_state->watermark_cb) { |
| CE_WATERMARK_INTR_ENABLE(targid, ctrl_addr); |
| } |
| } |
| A_TARGET_ACCESS_END(targid); |
| } |
| |
| void |
| CE_disable_any_copy_compl_intr(struct hif_pci_softc *sc) |
| { |
| adf_os_spin_lock(&sc->target_lock); |
| CE_disable_any_copy_compl_intr_nolock(sc); |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| /*Re-enable the copy compl interrupt if it has not been disabled before.*/ |
| void |
| CE_enable_any_copy_compl_intr(struct hif_pci_softc *sc) |
| { |
| adf_os_spin_lock(&sc->target_lock); |
| CE_enable_any_copy_compl_intr_nolock(sc); |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| void |
| CE_send_cb_register(struct CE_handle *copyeng, |
| CE_send_cb fn_ptr, |
| void *CE_send_context, |
| int disable_interrupts) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| CE_state->send_cb = fn_ptr; |
| CE_state->send_context = CE_send_context; |
| CE_per_engine_handler_adjust(CE_state, disable_interrupts); |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| void |
| CE_recv_cb_register(struct CE_handle *copyeng, |
| CE_recv_cb fn_ptr, |
| void *CE_recv_context, |
| int disable_interrupts) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| CE_state->recv_cb = fn_ptr; |
| CE_state->recv_context = CE_recv_context; |
| CE_per_engine_handler_adjust(CE_state, disable_interrupts); |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| void |
| CE_watermark_cb_register(struct CE_handle *copyeng, |
| CE_watermark_cb fn_ptr, |
| void *CE_wm_context) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| struct hif_pci_softc *sc = CE_state->sc; |
| |
| adf_os_spin_lock(&sc->target_lock); |
| CE_state->watermark_cb = fn_ptr; |
| CE_state->wm_context = CE_wm_context; |
| CE_per_engine_handler_adjust(CE_state, 0); |
| if (fn_ptr) { |
| CE_state->misc_cbs = 1; |
| } |
| adf_os_spin_unlock(&sc->target_lock); |
| } |
| |
| static unsigned int |
| roundup_pwr2(unsigned int n) |
| { |
| int i; |
| unsigned int test_pwr2; |
| |
| if (!(n & (n-1))) { |
| return n; /* already a power of 2 */ |
| } |
| |
| test_pwr2 = 4; |
| for (i=0; i<29; i++) { |
| if (test_pwr2 > n) { |
| return test_pwr2; |
| } |
| test_pwr2 = test_pwr2 << 1; |
| } |
| |
| A_ASSERT(0); /* n too large */ |
| return 0; |
| } |
| |
| bool CE_get_rx_pending(struct hif_pci_softc *sc) |
| { |
| int CE_id; |
| |
| for (CE_id=0; CE_id < sc->ce_count; CE_id++) { |
| struct CE_state *CE_state = sc->CE_id_to_state[CE_id]; |
| if (adf_os_atomic_read(&CE_state->rx_pending)) |
| return true; |
| } |
| |
| return false; |
| } |
| /* |
| * Initialize a Copy Engine based on caller-supplied attributes. |
| * This may be called once to initialize both source and destination |
| * rings or it may be called twice for separate source and destination |
| * initialization. It may be that only one side or the other is |
| * initialized by software/firmware. |
| */ |
| struct CE_handle * |
| CE_init(struct hif_pci_softc *sc, |
| unsigned int CE_id, |
| struct CE_attr *attr) |
| { |
| struct CE_state *CE_state; |
| u_int32_t ctrl_addr; |
| A_target_id_t targid; |
| unsigned int nentries; |
| adf_os_dma_addr_t base_addr; |
| struct ol_softc *scn = sc->ol_sc; |
| bool malloc_CE_state = false; |
| bool malloc_src_ring = false; |
| |
| A_ASSERT(CE_id < sc->ce_count); |
| ctrl_addr = CE_BASE_ADDRESS(CE_id); |
| adf_os_spin_lock(&sc->target_lock); |
| CE_state = sc->CE_id_to_state[CE_id]; |
| |
| |
| if (!CE_state) { |
| adf_os_spin_unlock(&sc->target_lock); |
| CE_state = (struct CE_state *)A_MALLOC(sizeof(*CE_state)); |
| if (!CE_state) { |
| dev_err(&sc->pdev->dev, "ath ERROR: CE_state has no mem\n"); |
| return NULL; |
| } else |
| malloc_CE_state = true; |
| A_MEMZERO(CE_state, sizeof(*CE_state)); |
| adf_os_spin_lock(&sc->target_lock); |
| if (!sc->CE_id_to_state[CE_id]) { /* re-check under lock */ |
| sc->CE_id_to_state[CE_id] = CE_state; |
| |
| CE_state->sc = sc; |
| CE_state->id = CE_id; |
| CE_state->ctrl_addr = ctrl_addr; |
| CE_state->state = CE_RUNNING; |
| CE_state->attr_flags = attr->flags; /* Save attribute flags */ |
| } else { |
| /* |
| * We released target_lock in order to allocate CE state, |
| * but someone else beat us to it. Continue, using that |
| * CE_state (and free the one we allocated). |
| */ |
| A_FREE(CE_state); |
| malloc_CE_state = false; |
| CE_state = sc->CE_id_to_state[CE_id]; |
| } |
| } |
| adf_os_spin_unlock(&sc->target_lock); |
| |
| adf_os_atomic_init(&CE_state->rx_pending); |
| if (attr == NULL) { |
| /* Already initialized; caller wants the handle */ |
| return (struct CE_handle *)CE_state; |
| } |
| |
| targid = TARGID(sc); |
| |
| if (CE_state->src_sz_max) { |
| A_ASSERT(CE_state->src_sz_max == attr->src_sz_max); |
| } else { |
| CE_state->src_sz_max = attr->src_sz_max; |
| } |
| |
| CE_init_ce_desc_event_log(CE_id, |
| attr->src_nentries + attr->dest_nentries); |
| |
| /* source ring setup */ |
| nentries = attr->src_nentries; |
| if (nentries) { |
| struct CE_ring_state *src_ring; |
| unsigned CE_nbytes; |
| char *ptr; |
| |
| nentries = roundup_pwr2(nentries); |
| if (CE_state->src_ring) { |
| A_ASSERT(CE_state->src_ring->nentries == nentries); |
| } else { |
| CE_nbytes = sizeof(struct CE_ring_state) |
| + (nentries * sizeof(void *)); /* per-send context */ |
| ptr = A_MALLOC(CE_nbytes); |
| if (!ptr) { |
| /* cannot allocate src ring. If the CE_state is allocated |
| * locally free CE_State and return error. */ |
| dev_err(&sc->pdev->dev, "ath ERROR: src ring has no mem\n"); |
| if (malloc_CE_state) { |
| /* allocated CE_state locally */ |
| adf_os_spin_lock(&sc->target_lock); |
| sc->CE_id_to_state[CE_id]= NULL; |
| adf_os_spin_unlock(&sc->target_lock); |
| A_FREE(CE_state); |
| malloc_CE_state = false; |
| } |
| return NULL; |
| } else { |
| /* we can allocate src ring. |
| * Mark that the src ring is allocated locally */ |
| malloc_src_ring = true; |
| } |
| A_MEMZERO(ptr, CE_nbytes); |
| |
| src_ring = CE_state->src_ring = (struct CE_ring_state *)ptr; |
| ptr += sizeof(struct CE_ring_state); |
| src_ring->nentries = nentries; |
| src_ring->nentries_mask = nentries-1; |
| A_TARGET_ACCESS_BEGIN_RET_PTR(targid); |
| src_ring->hw_index = |
| src_ring->sw_index = CE_SRC_RING_READ_IDX_GET(targid, ctrl_addr); |
| src_ring->write_index = CE_SRC_RING_WRITE_IDX_GET(targid, ctrl_addr); |
| A_TARGET_ACCESS_END_RET_PTR(targid); |
| src_ring->low_water_mark_nentries = 0; |
| src_ring->high_water_mark_nentries = nentries; |
| src_ring->per_transfer_context = (void **)ptr; |
| |
| /* Legacy platforms that do not support cache coherent DMA are unsupported */ |
| src_ring->base_addr_owner_space_unaligned = |
| pci_alloc_consistent(scn->sc_osdev->bdev, |
| (nentries * sizeof(struct CE_src_desc) + CE_DESC_RING_ALIGN), |
| &base_addr); |
| if (src_ring->base_addr_owner_space_unaligned == NULL) { |
| dev_err(&sc->pdev->dev, "ath ERROR: src ring has no DMA mem\n"); |
| goto error_no_dma_mem; |
| } |
| src_ring->base_addr_CE_space_unaligned = base_addr; |
| |
| |
| if (src_ring->base_addr_CE_space_unaligned & (CE_DESC_RING_ALIGN-1)) { |
| |
| src_ring->base_addr_CE_space = (src_ring->base_addr_CE_space_unaligned + |
| CE_DESC_RING_ALIGN-1) & ~(CE_DESC_RING_ALIGN-1); |
| |
| src_ring->base_addr_owner_space = (void *)(((size_t)src_ring->base_addr_owner_space_unaligned + |
| CE_DESC_RING_ALIGN-1) & ~(CE_DESC_RING_ALIGN-1)); |
| } else { |
| src_ring->base_addr_CE_space = src_ring->base_addr_CE_space_unaligned; |
| src_ring->base_addr_owner_space = src_ring->base_addr_owner_space_unaligned; |
| } |
| /* |
| * Also allocate a shadow src ring in regular mem to use for |
| * faster access. |
| */ |
| src_ring->shadow_base_unaligned = A_MALLOC( |
| nentries * sizeof(struct CE_src_desc) + CE_DESC_RING_ALIGN); |
| if (src_ring->shadow_base_unaligned == NULL) { |
| dev_err(&sc->pdev->dev, "ath ERROR: src ring has no shadow_base mem\n"); |
| goto error_no_dma_mem; |
| } |
| src_ring->shadow_base = (struct CE_src_desc *) |
| (((size_t) src_ring->shadow_base_unaligned + |
| CE_DESC_RING_ALIGN-1) & ~(CE_DESC_RING_ALIGN-1)); |
| |
| A_TARGET_ACCESS_BEGIN_RET_PTR(targid); |
| CE_SRC_RING_BASE_ADDR_SET(targid, ctrl_addr, src_ring->base_addr_CE_space); |
| CE_SRC_RING_SZ_SET(targid, ctrl_addr, nentries); |
| CE_SRC_RING_DMAX_SET(targid, ctrl_addr, attr->src_sz_max); |
| #ifdef BIG_ENDIAN_HOST |
| /* Enable source ring byte swap for big endian host */ |
| CE_SRC_RING_BYTE_SWAP_SET(targid, ctrl_addr, 1); |
| #endif |
| CE_SRC_RING_LOWMARK_SET(targid, ctrl_addr, 0); |
| CE_SRC_RING_HIGHMARK_SET(targid, ctrl_addr, nentries); |
| A_TARGET_ACCESS_END_RET_PTR(targid); |
| } |
| } |
| |
| /* destination ring setup */ |
| nentries = attr->dest_nentries; |
| if (nentries) { |
| struct CE_ring_state *dest_ring; |
| unsigned CE_nbytes; |
| char *ptr; |
| |
| nentries = roundup_pwr2(nentries); |
| if (CE_state->dest_ring) { |
| A_ASSERT(CE_state->dest_ring->nentries == nentries); |
| } else { |
| CE_nbytes = sizeof(struct CE_ring_state) |
| + (nentries * sizeof(void *)); /* per-recv context */ |
| ptr = A_MALLOC(CE_nbytes); |
| if (!ptr) { |
| /* cannot allocate dst ring. If the CE_state or src ring is allocated |
| * locally free CE_State and src ring and return error. */ |
| dev_err(&sc->pdev->dev, "ath ERROR: dest ring has no mem\n"); |
| if (malloc_src_ring) { |
| A_FREE(CE_state->src_ring); |
| CE_state->src_ring= NULL; |
| malloc_src_ring = false; |
| } |
| if (malloc_CE_state) { |
| /* allocated CE_state locally */ |
| adf_os_spin_lock(&sc->target_lock); |
| sc->CE_id_to_state[CE_id]= NULL; |
| adf_os_spin_unlock(&sc->target_lock); |
| A_FREE(CE_state); |
| malloc_CE_state = false; |
| } |
| return NULL; |
| } |
| A_MEMZERO(ptr, CE_nbytes); |
| |
| dest_ring = CE_state->dest_ring = (struct CE_ring_state *)ptr; |
| ptr += sizeof(struct CE_ring_state); |
| dest_ring->nentries = nentries; |
| dest_ring->nentries_mask = nentries-1; |
| A_TARGET_ACCESS_BEGIN_RET_PTR(targid); |
| dest_ring->sw_index = CE_DEST_RING_READ_IDX_GET(targid, ctrl_addr); |
| dest_ring->write_index = CE_DEST_RING_WRITE_IDX_GET(targid, ctrl_addr); |
| A_TARGET_ACCESS_END_RET_PTR(targid); |
| dest_ring->low_water_mark_nentries = 0; |
| dest_ring->high_water_mark_nentries = nentries; |
| dest_ring->per_transfer_context = (void **)ptr; |
| |
| /* Legacy platforms that do not support cache coherent DMA are unsupported */ |
| dest_ring->base_addr_owner_space_unaligned = |
| pci_alloc_consistent(scn->sc_osdev->bdev, |
| (nentries * sizeof(struct CE_dest_desc) + CE_DESC_RING_ALIGN), |
| &base_addr); |
| if (dest_ring->base_addr_owner_space_unaligned == NULL) { |
| dev_err(&sc->pdev->dev, "ath ERROR: dest ring has no DMA mem\n"); |
| goto error_no_dma_mem; |
| } |
| dest_ring->base_addr_CE_space_unaligned = base_addr; |
| |
| /* Correctly initialize memory to 0 to prevent garbage data |
| * crashing system when download firmware |
| */ |
| A_MEMZERO(dest_ring->base_addr_owner_space_unaligned, nentries * sizeof(struct CE_dest_desc) + CE_DESC_RING_ALIGN); |
| |
| if (dest_ring->base_addr_CE_space_unaligned & (CE_DESC_RING_ALIGN-1)) { |
| |
| dest_ring->base_addr_CE_space = (dest_ring->base_addr_CE_space_unaligned + |
| CE_DESC_RING_ALIGN-1) & ~(CE_DESC_RING_ALIGN-1); |
| |
| dest_ring->base_addr_owner_space = (void *)(((size_t)dest_ring->base_addr_owner_space_unaligned + |
| CE_DESC_RING_ALIGN-1) & ~(CE_DESC_RING_ALIGN-1)); |
| } else { |
| dest_ring->base_addr_CE_space = dest_ring->base_addr_CE_space_unaligned; |
| dest_ring->base_addr_owner_space = dest_ring->base_addr_owner_space_unaligned; |
| } |
| |
| A_TARGET_ACCESS_BEGIN_RET_PTR(targid); |
| CE_DEST_RING_BASE_ADDR_SET(targid, ctrl_addr, dest_ring->base_addr_CE_space); |
| CE_DEST_RING_SZ_SET(targid, ctrl_addr, nentries); |
| #ifdef BIG_ENDIAN_HOST |
| /* Enable Destination ring byte swap for big endian host */ |
| CE_DEST_RING_BYTE_SWAP_SET(targid, ctrl_addr, 1); |
| #endif |
| CE_DEST_RING_LOWMARK_SET(targid, ctrl_addr, 0); |
| CE_DEST_RING_HIGHMARK_SET(targid, ctrl_addr, nentries); |
| A_TARGET_ACCESS_END_RET_PTR(targid); |
| |
| /* epping */ |
| /* poll timer */ |
| if ((CE_state->attr_flags & CE_ATTR_ENABLE_POLL)) { |
| adf_os_timer_init(scn->adf_dev, &CE_state->poll_timer, |
| CE_poll_timeout, CE_state, ADF_DEFERRABLE_TIMER); |
| CE_state->timer_inited = true; |
| adf_os_timer_mod(&CE_state->poll_timer, CE_POLL_TIMEOUT); |
| } |
| } |
| } |
| |
| /* Enable CE error interrupts */ |
| A_TARGET_ACCESS_BEGIN_RET_PTR(targid); |
| CE_ERROR_INTR_ENABLE(targid, ctrl_addr); |
| A_TARGET_ACCESS_END_RET_PTR(targid); |
| |
| return (struct CE_handle *)CE_state; |
| |
| error_no_dma_mem: |
| CE_fini((struct CE_handle *)CE_state); |
| return NULL; |
| } |
| |
| void |
| CE_fini(struct CE_handle *copyeng) |
| { |
| struct CE_state *CE_state = (struct CE_state *)copyeng; |
| unsigned int CE_id = CE_state->id; |
| struct hif_pci_softc *sc = CE_state->sc; |
| struct ol_softc *scn = sc->ol_sc; |
| |
| CE_state->state = CE_UNUSED; |
| CE_state->sc->CE_id_to_state[CE_id] = NULL; |
| if (CE_state->src_ring) { |
| if (CE_state->src_ring->shadow_base_unaligned) |
| A_FREE(CE_state->src_ring->shadow_base_unaligned); |
| if (CE_state->src_ring->base_addr_owner_space_unaligned) |
| pci_free_consistent(scn->sc_osdev->bdev, |
| (CE_state->src_ring->nentries * sizeof(struct CE_src_desc) + CE_DESC_RING_ALIGN), |
| CE_state->src_ring->base_addr_owner_space_unaligned, CE_state->src_ring->base_addr_CE_space); |
| A_FREE(CE_state->src_ring); |
| } |
| if (CE_state->dest_ring) { |
| if (CE_state->dest_ring->base_addr_owner_space_unaligned) |
| pci_free_consistent(scn->sc_osdev->bdev, |
| (CE_state->dest_ring->nentries * sizeof(struct CE_dest_desc) + CE_DESC_RING_ALIGN), |
| CE_state->dest_ring->base_addr_owner_space_unaligned, CE_state->dest_ring->base_addr_CE_space); |
| A_FREE(CE_state->dest_ring); |
| |
| /* epping */ |
| if (CE_state->timer_inited) { |
| CE_state->timer_inited = false; |
| adf_os_timer_free(&CE_state->poll_timer); |
| } |
| } |
| A_FREE(CE_state); |
| } |
| |
| #ifdef IPA_UC_OFFLOAD |
| /* |
| * Copy engine should release resource to micro controller |
| * Micro controller needs |
| - Copy engine source descriptor base address |
| - Copy engine source descriptor size |
| - PCI BAR address to access copy engine regiser |
| */ |
| void CE_ipaGetResource(struct CE_handle *ce, |
| a_uint32_t *ce_sr_base_paddr, |
| a_uint32_t *ce_sr_ring_size, |
| a_uint32_t *ce_reg_paddr) |
| { |
| struct CE_state *CE_state = (struct CE_state *)ce; |
| a_uint32_t ring_loop; |
| struct CE_src_desc *ce_desc; |
| a_uint32_t bar_value; |
| struct hif_pci_softc *sc = CE_state->sc; |
| |
| if (CE_RUNNING != CE_state->state) |
| { |
| *ce_sr_base_paddr = 0; |
| *ce_sr_ring_size = 0; |
| return; |
| } |
| |
| /* Update default value for descriptor */ |
| for (ring_loop = 0; ring_loop < CE_state->src_ring->nentries; ring_loop++) |
| { |
| ce_desc = (struct CE_src_desc *) |
| ((char *)CE_state->src_ring->base_addr_owner_space + |
| ring_loop * (sizeof(struct CE_src_desc))); |
| /* Source pointer and ID, |
| * should be updated by uc dynamically |
| * ce_desc->src_ptr = buffer; |
| * ce_desc->meta_data = transfer_id; */ |
| /* No Byte SWAP */ |
| ce_desc->byte_swap = 0; |
| /* DL size |
| * pdev->download_len = |
| * sizeof(struct htt_host_tx_desc_t) + |
| * HTT_TX_HDR_SIZE_OUTER_HDR_MAX + |
| * HTT_TX_HDR_SIZE_802_1Q + |
| * HTT_TX_HDR_SIZE_LLC_SNAP + |
| * ol_cfg_tx_download_size(pdev->ctrl_pdev); */ |
| ce_desc->nbytes = 60; |
| /* Single fragment No gather */ |
| ce_desc->gather = 0; |
| } |
| |
| /* Get BAR address */ |
| hif_read_bar(CE_state->sc, &bar_value); |
| |
| *ce_sr_base_paddr = (a_uint32_t)CE_state->src_ring->base_addr_CE_space; |
| *ce_sr_ring_size = (a_uint32_t)CE_state->src_ring->nentries; |
| *ce_reg_paddr = bar_value + CE_BASE_ADDRESS(CE_state->id) + SR_WR_INDEX_ADDRESS; |
| return; |
| } |
| #endif /* IPA_UC_OFFLOAD */ |