/* * Copyright (c) 2015-2018 The Linux Foundation. All rights reserved. * * 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. */ #include "targcfg.h" #include "target_type.h" #include "qdf_lock.h" #include "qdf_status.h" #include "qdf_status.h" #include /* qdf_atomic_read */ #include #include "hif_io32.h" #include #include "regtable.h" #include #include "hif_main.h" #include "ce_api.h" #include "qdf_trace.h" #include "hif_debug.h" #include "qdf_module.h" void hif_ce_dump_target_memory(struct hif_softc *scn, void *ramdump_base, uint32_t address, uint32_t size) { uint32_t loc = address; uint32_t val = 0; uint32_t j = 0; u8 *temp = ramdump_base; if (Q_TARGET_ACCESS_BEGIN(scn) < 0) return; while (j < size) { val = hif_read32_mb(scn->mem + loc + j); qdf_mem_copy(temp, &val, 4); j += 4; temp += 4; } Q_TARGET_ACCESS_END(scn); } /* * TBDXXX: Should be a function call specific to each Target-type. * This convoluted macro converts from Target CPU Virtual Address * Space to CE Address Space. As part of this process, we * conservatively fetch the current PCIE_BAR. MOST of the time, * this should match the upper bits of PCI space for this device; * but that's not guaranteed. */ #ifdef QCA_WIFI_3_0 #define TARG_CPU_SPACE_TO_CE_SPACE(pci_addr, addr) \ (scn->mem_pa + addr) #else #define TARG_CPU_SPACE_TO_CE_SPACE(pci_addr, addr) \ (((hif_read32_mb((pci_addr) + \ (SOC_CORE_BASE_ADDRESS|CORE_CTRL_ADDRESS)) & 0x7ff) << 21) \ | 0x100000 | ((addr) & 0xfffff)) #endif #define TARG_CPU_SPACE_TO_CE_SPACE_IPQ4019(pci_addr, addr) \ (hif_read32_mb((pci_addr)+(WIFICMN_PCIE_BAR_REG_ADDRESS)) \ | ((addr) & 0xfffff)) #define TARG_CPU_SPACE_TO_CE_SPACE_AR900B(pci_addr, addr) \ (hif_read32_mb((pci_addr)+(WIFICMN_PCIE_BAR_REG_ADDRESS)) \ | 0x100000 | ((addr) & 0xfffff)) #define SRAM_BASE_ADDRESS 0xc0000 #define SRAM_END_ADDRESS 0x100000 #define WIFI0_IPQ4019_BAR 0xa000000 #define WIFI1_IPQ4019_BAR 0xa800000 /* Wait up to this many Ms for a Diagnostic Access CE operation to complete */ #define DIAG_ACCESS_CE_TIMEOUT_MS 10 /** * get_ce_phy_addr() - get the physical address of an soc virtual address * @sc: hif context * @address: soc virtual address * @target_type: target type being used. * * Return: soc physical address */ static qdf_dma_addr_t get_ce_phy_addr(struct hif_softc *sc, uint32_t address, unsigned int target_type) { qdf_dma_addr_t ce_phy_addr; struct hif_softc *scn = sc; unsigned int region = address & 0xfffff; unsigned int bar = address & 0xfff00000; unsigned int sramregion = 0; if ((target_type == TARGET_TYPE_IPQ4019) && (region >= SRAM_BASE_ADDRESS && region <= SRAM_END_ADDRESS) && (bar == WIFI0_IPQ4019_BAR || bar == WIFI1_IPQ4019_BAR || bar == 0)) { sramregion = 1; } if ((target_type == TARGET_TYPE_IPQ4019) && sramregion == 1) { ce_phy_addr = TARG_CPU_SPACE_TO_CE_SPACE_IPQ4019(sc->mem, address); } else if ((target_type == TARGET_TYPE_AR900B) || (target_type == TARGET_TYPE_QCA9984) || (target_type == TARGET_TYPE_IPQ4019) || (target_type == TARGET_TYPE_QCA9888)) { ce_phy_addr = TARG_CPU_SPACE_TO_CE_SPACE_AR900B(sc->mem, address); } else { ce_phy_addr = TARG_CPU_SPACE_TO_CE_SPACE(sc->mem, address); } return ce_phy_addr; } /* * Diagnostic read/write access is provided for startup/config/debug usage. * Caller must guarantee proper alignment, when applicable, and single user * at any moment. */ #define FW_SRAM_ADDRESS 0x000C0000 QDF_STATUS hif_diag_read_mem(struct hif_opaque_softc *hif_ctx, uint32_t address, uint8_t *data, int nbytes) { struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx); struct HIF_CE_state *hif_state = HIF_GET_CE_STATE(scn); QDF_STATUS status = QDF_STATUS_SUCCESS; qdf_dma_addr_t buf; unsigned int completed_nbytes, orig_nbytes, remaining_bytes; unsigned int id; unsigned int flags; struct CE_handle *ce_diag; qdf_dma_addr_t CE_data; /* Host buffer address in CE space */ qdf_dma_addr_t CE_data_base = 0; void *data_buf = NULL; int i; unsigned int mux_id = 0; unsigned int transaction_id = 0xffff; qdf_dma_addr_t ce_phy_addr = address; unsigned int toeplitz_hash_result; unsigned int user_flags = 0; unsigned int target_type = 0; unsigned int boundary_addr = 0; ce_diag = hif_state->ce_diag; if (ce_diag == NULL) { HIF_ERROR("%s: DIAG CE not present", __func__); return QDF_STATUS_E_INVAL; } /* not supporting diag ce on srng based systems, therefore we know this * isn't an srng based system */ transaction_id = (mux_id & MUX_ID_MASK) | (transaction_id & TRANSACTION_ID_MASK); #ifdef QCA_WIFI_3_0 user_flags &= DESC_DATA_FLAG_MASK; #endif target_type = (hif_get_target_info_handle(hif_ctx))->target_type; /* This code cannot handle reads to non-memory space. Redirect to the * register read fn but preserve the multi word read capability of * this fn */ if ((target_type == TARGET_TYPE_IPQ4019) || (target_type == TARGET_TYPE_AR900B) || (target_type == TARGET_TYPE_QCA9984) || (target_type == TARGET_TYPE_AR9888) || (target_type == TARGET_TYPE_QCA9888)) boundary_addr = FW_SRAM_ADDRESS; else boundary_addr = DRAM_BASE_ADDRESS; if (address < boundary_addr) { if ((address & 0x3) || ((uintptr_t) data & 0x3)) return QDF_STATUS_E_INVAL; while ((nbytes >= 4) && (QDF_STATUS_SUCCESS == (status = hif_diag_read_access(hif_ctx, address, (uint32_t *)data)))) { nbytes -= sizeof(uint32_t); address += sizeof(uint32_t); data += sizeof(uint32_t); } return status; } A_TARGET_ACCESS_LIKELY(scn); /* * Allocate a temporary bounce buffer to hold caller's data * to be DMA'ed from Target. This guarantees * 1) 4-byte alignment * 2) Buffer in DMA-able space */ orig_nbytes = nbytes; data_buf = qdf_mem_alloc_consistent(scn->qdf_dev, scn->qdf_dev->dev, orig_nbytes, &CE_data_base); if (!data_buf) { status = QDF_STATUS_E_NOMEM; goto done; } qdf_mem_set(data_buf, orig_nbytes, 0); qdf_mem_dma_sync_single_for_device(scn->qdf_dev, CE_data_base, orig_nbytes, DMA_FROM_DEVICE); remaining_bytes = orig_nbytes; CE_data = CE_data_base; while (remaining_bytes) { nbytes = min(remaining_bytes, DIAG_TRANSFER_LIMIT); { status = ce_recv_buf_enqueue(ce_diag, NULL, CE_data); if (status != QDF_STATUS_SUCCESS) goto done; } if (Q_TARGET_ACCESS_BEGIN(scn) < 0) { status = QDF_STATUS_E_FAILURE; goto done; } /* convert soc virtual address to physical address */ ce_phy_addr = get_ce_phy_addr(scn, address, target_type); if (Q_TARGET_ACCESS_END(scn) < 0) { status = QDF_STATUS_E_FAILURE; goto done; } /* Request CE to send from Target(!) * address to Host buffer */ status = ce_send(ce_diag, NULL, ce_phy_addr, nbytes, transaction_id, 0, user_flags); if (status != QDF_STATUS_SUCCESS) goto done; i = 0; while (ce_completed_send_next(ce_diag, NULL, NULL, &buf, &completed_nbytes, &id, NULL, NULL, &toeplitz_hash_result) != QDF_STATUS_SUCCESS) { qdf_mdelay(1); if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) { status = QDF_STATUS_E_BUSY; goto done; } } if (nbytes != completed_nbytes) { status = QDF_STATUS_E_FAILURE; goto done; } if (buf != ce_phy_addr) { status = QDF_STATUS_E_FAILURE; goto done; } i = 0; while (ce_completed_recv_next (ce_diag, NULL, NULL, &buf, &completed_nbytes, &id, &flags) != QDF_STATUS_SUCCESS) { qdf_mdelay(1); if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) { status = QDF_STATUS_E_BUSY; goto done; } } if (nbytes != completed_nbytes) { status = QDF_STATUS_E_FAILURE; goto done; } if (buf != CE_data) { status = QDF_STATUS_E_FAILURE; goto done; } remaining_bytes -= nbytes; address += nbytes; CE_data += nbytes; } done: A_TARGET_ACCESS_UNLIKELY(scn); if (status == QDF_STATUS_SUCCESS) qdf_mem_copy(data, data_buf, orig_nbytes); else HIF_ERROR("%s failure (0x%x)", __func__, address); if (data_buf) qdf_mem_free_consistent(scn->qdf_dev, scn->qdf_dev->dev, orig_nbytes, data_buf, CE_data_base, 0); return status; } qdf_export_symbol(hif_diag_read_mem); /* Read 4-byte aligned data from Target memory or register */ QDF_STATUS hif_diag_read_access(struct hif_opaque_softc *hif_ctx, uint32_t address, uint32_t *data) { struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx); if (address >= DRAM_BASE_ADDRESS) { /* Assume range doesn't cross this boundary */ return hif_diag_read_mem(hif_ctx, address, (uint8_t *) data, sizeof(uint32_t)); } else { if (Q_TARGET_ACCESS_BEGIN(scn) < 0) return QDF_STATUS_E_FAILURE; *data = A_TARGET_READ(scn, address); if (Q_TARGET_ACCESS_END(scn) < 0) return QDF_STATUS_E_FAILURE; return QDF_STATUS_SUCCESS; } } /** * hif_diag_write_mem() - write data into the soc memory * @hif_ctx: hif context * @address: soc virtual address * @data: data to copy into the soc address * @nbytes: number of bytes to coppy */ QDF_STATUS hif_diag_write_mem(struct hif_opaque_softc *hif_ctx, uint32_t address, uint8_t *data, int nbytes) { struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx); struct HIF_CE_state *hif_state = HIF_GET_CE_STATE(hif_ctx); QDF_STATUS status = QDF_STATUS_SUCCESS; qdf_dma_addr_t buf; unsigned int completed_nbytes, orig_nbytes, remaining_bytes; unsigned int id; unsigned int flags; struct CE_handle *ce_diag; void *data_buf = NULL; qdf_dma_addr_t CE_data; /* Host buffer address in CE space */ qdf_dma_addr_t CE_data_base = 0; int i; unsigned int mux_id = 0; unsigned int transaction_id = 0xffff; qdf_dma_addr_t ce_phy_addr = address; unsigned int toeplitz_hash_result; unsigned int user_flags = 0; unsigned int target_type = 0; ce_diag = hif_state->ce_diag; if (ce_diag == NULL) { HIF_ERROR("%s: DIAG CE not present", __func__); return QDF_STATUS_E_INVAL; } /* not supporting diag ce on srng based systems, therefore we know this * isn't an srng based system */ transaction_id = (mux_id & MUX_ID_MASK) | (transaction_id & TRANSACTION_ID_MASK); #ifdef QCA_WIFI_3_0 user_flags &= DESC_DATA_FLAG_MASK; #endif A_TARGET_ACCESS_LIKELY(scn); /* * Allocate a temporary bounce buffer to hold caller's data * to be DMA'ed to Target. This guarantees * 1) 4-byte alignment * 2) Buffer in DMA-able space */ orig_nbytes = nbytes; data_buf = qdf_mem_alloc_consistent(scn->qdf_dev, scn->qdf_dev->dev, orig_nbytes, &CE_data_base); if (!data_buf) { status = QDF_STATUS_E_NOMEM; goto done; } /* Copy caller's data to allocated DMA buf */ qdf_mem_copy(data_buf, data, orig_nbytes); qdf_mem_dma_sync_single_for_device(scn->qdf_dev, CE_data_base, orig_nbytes, DMA_TO_DEVICE); target_type = (hif_get_target_info_handle(hif_ctx))->target_type; if (Q_TARGET_ACCESS_BEGIN(scn) < 0) { status = QDF_STATUS_E_FAILURE; goto done; } /* convert soc virtual address to physical address */ ce_phy_addr = get_ce_phy_addr(scn, address, target_type); if (Q_TARGET_ACCESS_END(scn) < 0) { status = QDF_STATUS_E_FAILURE; goto done; } remaining_bytes = orig_nbytes; CE_data = CE_data_base; while (remaining_bytes) { nbytes = min(remaining_bytes, DIAG_TRANSFER_LIMIT); /* Set up to receive directly into Target(!) address */ status = ce_recv_buf_enqueue(ce_diag, NULL, ce_phy_addr); if (status != QDF_STATUS_SUCCESS) goto done; /* * Request CE to send caller-supplied data that * was copied to bounce buffer to Target(!) address. */ status = ce_send(ce_diag, NULL, (qdf_dma_addr_t) CE_data, nbytes, transaction_id, 0, user_flags); if (status != QDF_STATUS_SUCCESS) goto done; /* poll for transfer complete */ i = 0; while (ce_completed_send_next(ce_diag, NULL, NULL, &buf, &completed_nbytes, &id, NULL, NULL, &toeplitz_hash_result) != QDF_STATUS_SUCCESS) { qdf_mdelay(1); if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) { status = QDF_STATUS_E_BUSY; goto done; } } if (nbytes != completed_nbytes) { status = QDF_STATUS_E_FAILURE; goto done; } if (buf != CE_data) { status = QDF_STATUS_E_FAILURE; goto done; } i = 0; while (ce_completed_recv_next (ce_diag, NULL, NULL, &buf, &completed_nbytes, &id, &flags) != QDF_STATUS_SUCCESS) { qdf_mdelay(1); if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) { status = QDF_STATUS_E_BUSY; goto done; } } if (nbytes != completed_nbytes) { status = QDF_STATUS_E_FAILURE; goto done; } if (buf != ce_phy_addr) { status = QDF_STATUS_E_FAILURE; goto done; } remaining_bytes -= nbytes; address += nbytes; CE_data += nbytes; } done: A_TARGET_ACCESS_UNLIKELY(scn); if (data_buf) { qdf_mem_free_consistent(scn->qdf_dev, scn->qdf_dev->dev, orig_nbytes, data_buf, CE_data_base, 0); } if (status != QDF_STATUS_SUCCESS) { HIF_ERROR("%s failure (0x%llx)", __func__, (uint64_t)ce_phy_addr); } return status; } /* Write 4B data to Target memory or register */ QDF_STATUS hif_diag_write_access(struct hif_opaque_softc *hif_ctx, uint32_t address, uint32_t data) { struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx); if (address >= DRAM_BASE_ADDRESS) { /* Assume range doesn't cross this boundary */ uint32_t data_buf = data; return hif_diag_write_mem(hif_ctx, address, (uint8_t *) &data_buf, sizeof(uint32_t)); } else { if (Q_TARGET_ACCESS_BEGIN(scn) < 0) return QDF_STATUS_E_FAILURE; A_TARGET_WRITE(scn, address, data); if (Q_TARGET_ACCESS_END(scn) < 0) return QDF_STATUS_E_FAILURE; return QDF_STATUS_SUCCESS; } }