| /* |
| * (C) Copyright 2012 Quantenna Communications Inc. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| /* |
| * Quantenna HBM skb payload pool |
| */ |
| #include <linux/kernel.h> |
| #include <linux/cache.h> |
| #include <linux/module.h> |
| #include <linux/mm.h> |
| #include <linux/proc_fs.h> |
| #include <linux/seq_file.h> |
| #include <linux/skbuff.h> |
| #include <qtn/dmautil.h> |
| #include <asm/io.h> |
| |
| #include <common/queue.h> |
| |
| #include <qtn/topaz_hbm.h> |
| #include <qtn/dmautil.h> |
| |
| #include <net80211/if_ethersubr.h> |
| |
| #define isspace(c) ((((c) == ' ') || (((unsigned int)((c) - 9)) <= (13 - 9)))) |
| |
| #define TOPAZ_HBM_PROC_FILENAME "topaz_hbm" |
| #define TOPAZ_HBM_IF_PROC_NAME "topaz_hbm_if" |
| |
| #define HBM_BUF_DEPLETION_TH (20) |
| #define HBM_BUF_POLL_S_INTRVAL (10 * HZ) |
| #define HBM_BUF_POLL_L_INTRVAL (60 * HZ) |
| #define HBM_BUF_MINIMUM_AVAIL_NUM (3) |
| #define HBM_BUF_MINIMUM_REL_NUM (10 * HBM_BUF_POLL_S_INTRVAL) |
| |
| typedef enum hbm_if_usr_cmd { |
| HBM_IF_CMD_DUMPCTL = 0, |
| HBM_IF_CMD_STATS = 1, |
| HBM_IF_MAX_CMD, |
| } hbm_if_usr_cmd; |
| |
| #define HBM_IF_KEY_DUMPCTL "dumpctl" |
| #define HBM_IF_KEY_STATS "stats" |
| static char* str_cmd[HBM_IF_MAX_CMD] = { |
| HBM_IF_KEY_DUMPCTL, |
| HBM_IF_KEY_STATS, |
| }; |
| |
| typedef enum hbm_stats { |
| HBM_CNT_INVALID_POOL = 0, |
| HBM_CNT_MISALIGNED = 1, |
| HBM_CNT_MAGIC_CORRUPTED = 2, |
| HBM_CNT_QUARANTINE_CORRUPTED = 3, |
| HBM_CNT_QUARANTINE_ALLOC_FAIL = 4, |
| HBM_CNT_QUARANTINE_OK = 5, |
| HBM_CNT_NUM, |
| } hbm_stats; |
| |
| struct hbm_pool_cnt { |
| uint32_t prev_release_cnt; |
| uint32_t pool_depleted_cnt; |
| }; |
| struct topaz_hbm_mnt { |
| uint32_t prev_unflow_cnt; |
| uint32_t unflow_flag; |
| struct hbm_pool_cnt wmac_pl; |
| struct hbm_pool_cnt emac_pl; |
| }; |
| |
| static DEFINE_TIMER(hbm_timer, NULL, 0, 0); |
| static uint32_t topaz_hbm_stats[HBM_CNT_NUM] = {0}; |
| static const char *topaz_hbm_stats_names[HBM_CNT_NUM] = { |
| "Invalid pool", |
| "Misaligned pointer", |
| "Magic corrupted", |
| "Quarantine corrupted buffer", |
| "Quarantine allocation fail", |
| "Quarantine ok", |
| }; |
| #define HBM_STATS(_idx, _num) (topaz_hbm_stats[(_idx)] += (_num)) |
| |
| #ifdef TOPAZ_EMAC_NULL_BUF_WR |
| #define HBM_UFLOW_RECOVER_TH 32 |
| void __attribute__((section(".sram.data")))(*topaz_emac_null_buf_del_cb)(void) = NULL; |
| EXPORT_SYMBOL(topaz_emac_null_buf_del_cb); |
| #endif |
| |
| static const char *topaz_hbm_requestor_names[TOPAZ_HBM_MASTER_COUNT] = TOPAZ_HBM_REQUESTOR_NAMES; |
| |
| unsigned int topaz_hbm_pool_available(int8_t pool) |
| { |
| uint32_t wr_ptr; |
| uint32_t rd_ptr; |
| static const unsigned int TOPAZ_HBM_MAX_POOL_COUNT = (1 << 16); |
| |
| if (!topaz_hbm_pool_valid(pool)) { |
| printk(KERN_ERR"%s: Invalid pool %d\n", __func__, pool); |
| return TOPAZ_HBM_MAX_POOL_COUNT; |
| } |
| |
| wr_ptr = readl(TOPAZ_HBM_WR_PTR(pool)); |
| rd_ptr = readl(TOPAZ_HBM_RD_PTR(pool)); |
| |
| if (wr_ptr >= rd_ptr) |
| return (wr_ptr - rd_ptr); |
| else |
| return (TOPAZ_HBM_MAX_POOL_COUNT - rd_ptr + wr_ptr); |
| } |
| EXPORT_SYMBOL(topaz_hbm_pool_available); |
| |
| #define HBM_DUMP_SORT_ORDER_INV_BASE 100 |
| typedef enum hbm_dump_sort_type { |
| HBM_DUMP_SORT_ADDR = 0, /* lower addr first */ |
| HBM_DUMP_SORT_JIFF, /* newest freed first */ |
| HBM_DUMP_SORT_BAD_MAGIC, /* bad magic first */ |
| HBM_DUMP_SORT_ADDR_MAX = HBM_DUMP_SORT_ORDER_INV_BASE - 1, |
| } hbm_dump_sort_type; |
| |
| static hbm_dump_sort_type topaz_hbm_dump_sort_type = HBM_DUMP_SORT_ADDR; |
| static int topaz_hbm_dump_sort_range_min = 0; /* meaning dependent on sort type */ |
| static int topaz_hbm_dump_sort_range_max = 0xFFFFFFFF; /* meaning dependent on sort type */ |
| static int topaz_hbm_dump_num = 5; /* max dump number */ |
| static int topaz_hbm_dump_len = 128; /* bytes dump at head */ |
| static int topaz_hbm_dump_taillen = 32; /* bytes dump at tail */ |
| static int topaz_hbm_dumped_num = 0; /* currently dumpped number */ |
| |
| #define TOPAZ_HBM_POOL_SIZE_MAX (TOPAZ_HBM_BUF_EMAC_RX_COUNT + 1) |
| uint32_t* topaz_hbm_dump_bufs_sorted[TOPAZ_HBM_POOL_SIZE_MAX] = {0}; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| static int topaz_hbm_stat_rd(struct seq_file *sfile, void *data) |
| #else |
| |
| static int topaz_hbm_stat_rd(char *page, char **start, off_t offset, |
| int count, int *eof, void *data) |
| #endif |
| { |
| |
| uint32_t wr_ptr; |
| uint32_t rd_ptr; |
| int ret = 0; |
| unsigned long flags; |
| int req_rel_diff = 0; |
| int req_rel_perpool_diff[TOPAZ_HBM_POOL_COUNT]; |
| int master, pool; |
| uint32_t overflow; |
| uint32_t underflow; |
| int allocated; |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0) |
| char *p = page; |
| #endif |
| |
| local_irq_save(flags); |
| /* offsets for initial pool loading */ |
| req_rel_perpool_diff[TOPAZ_HBM_BUF_EMAC_RX_POOL] = TOPAZ_HBM_BUF_EMAC_RX_COUNT; |
| req_rel_perpool_diff[TOPAZ_HBM_BUF_WMAC_RX_POOL] = TOPAZ_HBM_BUF_WMAC_RX_COUNT; |
| req_rel_perpool_diff[TOPAZ_HBM_AUC_FEEDBACK_POOL] = 0; |
| req_rel_perpool_diff[TOPAZ_HBM_EMAC_TX_DONE_POOL] = 0; |
| |
| for (pool = 0; pool < TOPAZ_HBM_POOL_COUNT; ++pool) { |
| for (master = 0; master < TOPAZ_HBM_MASTER_COUNT; ++master) { |
| uint32_t req = readl(TOPAZ_HBM_POOL_REQUEST_CNT(master, pool)); |
| uint32_t rel = readl(TOPAZ_HBM_POOL_RELEASE_CNT(master, pool)); |
| |
| req_rel_perpool_diff[pool] += req; |
| req_rel_perpool_diff[pool] -= rel; |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| seq_printf(sfile, "master %5s pool %d req %u rel %u\n", |
| topaz_hbm_requestor_names[master], pool, req, rel); |
| #else |
| p += sprintf(p, "master %5s pool %d req %u rel %u\n", |
| topaz_hbm_requestor_names[master], pool, req, rel); |
| #endif |
| } |
| } |
| |
| for (pool = 0; pool < TOPAZ_HBM_POOL_COUNT; ++pool) { |
| req_rel_diff += req_rel_perpool_diff[pool]; |
| wr_ptr = readl(TOPAZ_HBM_WR_PTR(pool)); |
| rd_ptr = readl(TOPAZ_HBM_RD_PTR(pool)); |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| seq_printf(sfile, "pool %u rd_ptr %u wr_ptr %u\n", pool, rd_ptr, wr_ptr); |
| #else |
| p += sprintf(p, "pool %u rd_ptr %u wr_ptr %u\n", pool, rd_ptr, wr_ptr); |
| #endif |
| } |
| |
| overflow = readl(TOPAZ_HBM_OVERFLOW_CNT); |
| underflow = readl(TOPAZ_HBM_UNDERFLOW_CNT); |
| allocated = req_rel_diff - underflow; |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| seq_printf(sfile, "underflow %u overflow %u req rel diff %d allocated %d\n", |
| underflow, overflow, req_rel_diff, allocated); |
| #else |
| p += sprintf(p, "underflow %u overflow %u req rel diff %d allocated %d\n", |
| underflow, overflow, req_rel_diff, allocated); |
| #endif |
| if (overflow) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| seq_printf(sfile, "ERROR: overflow counter must be zero\n"); |
| #else |
| p += sprintf(p, "ERROR: overflow counter must be zero\n"); |
| #endif |
| } |
| |
| if (underflow) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| seq_printf(sfile, "WARNING: underflow counter is non zero, may need to increase pool\n"); |
| #else |
| p += sprintf(p, "WARNING: underflow counter is non zero, may need to increase pool\n"); |
| #endif |
| } |
| |
| for (pool = 0; pool < TOPAZ_HBM_POOL_COUNT; ++pool) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| seq_printf(sfile, "pool %d req rel diff %d, available %u\n", pool, req_rel_perpool_diff[pool], |
| topaz_hbm_pool_available(pool)); |
| #else |
| p += sprintf(p, "pool %d req rel diff %d, available %u\n", pool, req_rel_perpool_diff[pool], |
| topaz_hbm_pool_available(pool)); |
| #endif |
| } |
| |
| local_irq_restore(flags); |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| return ret; |
| #else |
| *eof = 1; |
| return p - page; |
| #endif |
| } |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| static int topaz_hbm_stat_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, topaz_hbm_stat_rd, NULL); |
| } |
| |
| static const struct file_operations topaz_hbm_stat_fops = { |
| .owner = THIS_MODULE, |
| .open = topaz_hbm_stat_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| #endif |
| |
| static int __init topaz_hbm_stat_init(void) |
| { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| if (!proc_create(TOPAZ_HBM_PROC_FILENAME, 0x400, NULL, &topaz_hbm_stat_fops)) |
| return -ENOMEM; |
| #else |
| if (!create_proc_read_entry(TOPAZ_HBM_PROC_FILENAME, 0, |
| NULL, topaz_hbm_stat_rd, NULL)) { |
| return -EEXIST; |
| } |
| #endif |
| return 0; |
| } |
| |
| static void __exit topaz_hbm_stat_exit(void) |
| { |
| remove_proc_entry(TOPAZ_HBM_PROC_FILENAME, 0); |
| } |
| |
| void topaz_hbm_init_pool_list(unsigned long *const pool_list, const uint16_t payload_count_s, |
| const uintptr_t payloads_bus, const uint32_t payload_size, |
| const uint32_t payload_headroom, const int8_t pool) |
| { |
| uint32_t i; |
| const uint16_t payload_count = (1 << payload_count_s); |
| |
| topaz_hbm_init((void *) virt_to_bus(pool_list), payload_count_s, pool, 0); |
| |
| if (payloads_bus) { |
| for (i = 0; i < payload_count; i++) { |
| uintptr_t buf_bus = payloads_bus + (i * payload_size) + payload_headroom; |
| uint32_t *_p = bus_to_virt(buf_bus); |
| uint32_t *_m = topaz_hbm_buf_get_meta(_p); |
| uint32_t *enqueuep = _m - HBM_HR_OFFSET_ENQ_CNT; |
| uint32_t *freep = _m - HBM_HR_OFFSET_FREE_CNT; |
| uint32_t *statep = _m - HBM_HR_OFFSET_STATE; |
| uint32_t *magicp = _p - HBM_HR_OFFSET_MAGIC; |
| uint32_t *guardp =(uint32_t*)((uint32_t)_p + payload_size - payload_headroom - |
| TOPAZ_HBM_PAYLOAD_END_GUARD_SIZE); |
| #if TOPAZ_HBM_BUF_EXTERNAL_META |
| uint32_t *meta_ptr_p = _p - HBM_HR_OFFSET_META_PTR; |
| uint32_t *meta_backptr_p = _m - HBM_HR_OFFSET_META_PTR; |
| #endif |
| int j; |
| #if TOPAZ_HBM_DEBUG_STAMPS |
| uint32_t *jiffp = _m - HBM_HR_OFFSET_FREE_JIFF; |
| uint32_t *ownerp = _m - HBM_HR_OFFSET_OWNER; |
| arc_write_uncached_32(jiffp, jiffies); |
| arc_write_uncached_32(ownerp, TOPAZ_HBM_OWNER_INIT); |
| #endif |
| /* always setup magic and guard area to provide minimum detection */ |
| arc_write_uncached_32(magicp, TOPAZ_HBM_BUF_GUARD_MAGIC); |
| arc_write_uncached_32(statep, 0); |
| for (j = 0; j < (TOPAZ_HBM_PAYLOAD_END_GUARD_SIZE >> 2); j++) { |
| arc_write_uncached_32((guardp + j), TOPAZ_HBM_BUF_GUARD_MAGIC); |
| } |
| arc_write_uncached_32(enqueuep, 1); |
| arc_write_uncached_32(freep, 0); |
| |
| #if TOPAZ_HBM_BUF_EXTERNAL_META |
| arc_write_uncached_32(meta_ptr_p, virt_to_bus(_m)); |
| arc_write_uncached_32(meta_backptr_p, buf_bus); |
| #endif |
| |
| topaz_hbm_put_buf((void *) buf_bus, pool); |
| } |
| } |
| |
| printk(KERN_INFO "%s pool %u pool_list 0x%p bus_range 0x%lx to 0x%lx sz %u count %u\n", |
| __FUNCTION__, pool, pool_list, |
| payloads_bus, payloads_bus + payload_size * payload_count, |
| payload_size, payload_count); |
| } |
| |
| static int g_pools_inited = 0; |
| |
| static void topaz_hbm_init_payload_pools(void) |
| { |
| unsigned long flags; |
| uintptr_t *topaz_hbm_emac_rx_ptrs = (void *) (RUBY_SRAM_BEGIN + TOPAZ_HBM_POOL_EMAC_RX_START); |
| uintptr_t *topaz_hbm_wmac_rx_ptrs = (void *) (RUBY_SRAM_BEGIN + TOPAZ_HBM_POOL_WMAC_RX_START); |
| uintptr_t *topaz_hbm_emac_free_ptrs = (void *) (RUBY_SRAM_BEGIN + TOPAZ_HBM_POOL_EMAC_TX_DONE_START); |
| |
| printk("HBM pool: emac rx 0x%x to 0x%x, wmac rx 0x%x to 0x%x\n", |
| TOPAZ_HBM_POOL_EMAC_RX_START, |
| TOPAZ_HBM_POOL_EMAC_RX_END, |
| TOPAZ_HBM_POOL_WMAC_RX_START, |
| TOPAZ_HBM_POOL_WMAC_RX_END); |
| |
| memset((void *) (RUBY_DRAM_BEGIN + TOPAZ_HBM_BUF_EMAC_RX_BASE), TOPAZ_HBM_BUF_PAYLOAD_POISON, |
| TOPAZ_HBM_BUF_EMAC_RX_TOTAL); |
| memset((void *) (RUBY_DRAM_BEGIN + TOPAZ_HBM_BUF_WMAC_RX_BASE), TOPAZ_HBM_BUF_PAYLOAD_POISON, |
| TOPAZ_HBM_BUF_WMAC_RX_TOTAL); |
| flush_and_inv_dcache_sizerange_safe((void *) (RUBY_DRAM_BEGIN + TOPAZ_HBM_BUF_EMAC_RX_BASE), TOPAZ_HBM_BUF_EMAC_RX_TOTAL); |
| flush_and_inv_dcache_sizerange_safe((void *) (RUBY_DRAM_BEGIN + TOPAZ_HBM_BUF_WMAC_RX_BASE), TOPAZ_HBM_BUF_WMAC_RX_TOTAL); |
| |
| memset((void *) (RUBY_DRAM_BEGIN + TOPAZ_HBM_BUF_META_BASE), TOPAZ_HBM_BUF_PAYLOAD_POISON, |
| TOPAZ_HBM_BUF_META_TOTAL); |
| flush_and_inv_dcache_sizerange_safe((void *)(RUBY_DRAM_BEGIN + TOPAZ_HBM_BUF_META_BASE), |
| TOPAZ_HBM_BUF_META_TOTAL); |
| #if TOPAZ_HBM_BUF_EXTERNAL_META |
| printk("HBM meta: emac rx 0x%x to 0x%x, wmac rx 0x%x to 0x%x\n", |
| TOPAZ_HBM_BUF_META_EMAC_RX_BASE, |
| TOPAZ_HBM_BUF_META_EMAC_RX_END, |
| TOPAZ_HBM_BUF_META_WMAC_RX_BASE, |
| TOPAZ_HBM_BUF_META_WMAC_RX_END); |
| #else |
| printk("HBM used internal meta\n"); |
| #endif |
| |
| local_irq_save(flags); |
| |
| topaz_hbm_init_pool_list(topaz_hbm_emac_rx_ptrs, TOPAZ_HBM_BUF_EMAC_RX_COUNT_S, |
| RUBY_DRAM_BUS_BEGIN + TOPAZ_HBM_BUF_EMAC_RX_BASE, |
| TOPAZ_HBM_BUF_EMAC_RX_SIZE, TOPAZ_HBM_PAYLOAD_HEADROOM, |
| TOPAZ_HBM_BUF_EMAC_RX_POOL); |
| topaz_hbm_init_pool_list(topaz_hbm_wmac_rx_ptrs, TOPAZ_HBM_BUF_WMAC_RX_COUNT_S, |
| RUBY_DRAM_BUS_BEGIN + TOPAZ_HBM_BUF_WMAC_RX_BASE, |
| TOPAZ_HBM_BUF_WMAC_RX_SIZE, TOPAZ_HBM_PAYLOAD_HEADROOM, |
| TOPAZ_HBM_BUF_WMAC_RX_POOL); |
| topaz_hbm_init_pool_list(topaz_hbm_emac_free_ptrs, TOPAZ_HBM_EMAC_TX_DONE_COUNT_S, |
| 0, 0, 0, TOPAZ_HBM_EMAC_TX_DONE_POOL); |
| |
| local_irq_restore(flags); |
| |
| g_pools_inited = 1; |
| } |
| |
| int topaz_hbm_handle_buf_err(void *const buf_virt, int8_t dest_pool) |
| { |
| if (!topaz_hbm_buf_ptr_valid(buf_virt)) { |
| HBM_STATS(HBM_CNT_MISALIGNED, 1); |
| printk(KERN_CRIT "%s: buf 0x%x misaligned: pool %u\n", |
| __FUNCTION__, |
| (unsigned int)buf_virt, dest_pool); |
| return 0; |
| } |
| |
| HBM_STATS(HBM_CNT_MAGIC_CORRUPTED, 1); |
| hbm_buf_fix_buf_magic(buf_virt); |
| |
| return 1; |
| } |
| |
| void __attribute__((section(".sram.text"))) topaz_hbm_filter_txdone_buf(void *const buf_bus) |
| { |
| const int8_t dest_pool = topaz_hbm_payload_get_pool_bus(buf_bus); |
| |
| if (dest_pool == TOPAZ_HBM_BUF_WMAC_RX_POOL || |
| dest_pool == TOPAZ_HBM_BUF_EMAC_RX_POOL) { |
| uint32_t *const _p = bus_to_virt((uintptr_t) buf_bus); |
| uint32_t *_m = topaz_hbm_buf_get_meta(_p); |
| uint32_t *const enqueuep = _m - HBM_HR_OFFSET_ENQ_CNT; |
| uint32_t *const freep = _m - HBM_HR_OFFSET_FREE_CNT; |
| const uint32_t ec = arc_read_uncached_32(enqueuep); |
| const uint32_t fc = arc_read_uncached_32(freep) + 1; |
| const bool release = (ec && (fc == ec)); |
| |
| if (release) { |
| /* only fix magic corruption when we are sure no one else is using it right now */ |
| if (TOPAZ_HBM_BUF_MAGIC_CHK_ALLPOOL || (dest_pool == TOPAZ_HBM_BUF_WMAC_RX_POOL)) { |
| int state = hbm_buf_check_buf_magic(_p); |
| if (unlikely(state)) { |
| if (!topaz_hbm_handle_buf_err(_p, dest_pool)) { |
| /* shouldn't put it back to pool */ |
| return; |
| } |
| } |
| } |
| #if TOPAZ_HBM_DEBUG_STAMPS |
| uint32_t *jiffp = _m - HBM_HR_OFFSET_FREE_JIFF; |
| uint32_t *ownerp = _m - HBM_HR_OFFSET_OWNER; |
| const uint32_t owner = arc_read_uncached_32(ownerp); |
| const uint8_t owner1 = (owner & 0xF) >> 0; |
| |
| if (owner1 == TOPAZ_HBM_OWNER_FREE) { |
| /* |
| * Double free check is already broken because a lot of free places |
| * doesn't update the owner, both VB and PCIe platform. |
| */ |
| /* |
| uint32_t *sizep = _m - HBM_HR_OFFSET_SIZE; |
| const uint32_t size = arc_read_uncached_32(sizep); |
| printk(KERN_ERR "%s: double free of buf_bus %p size %u owner %08x\n", |
| __FUNCTION__, buf_bus, size, owner); |
| topaz_hbm_buf_show(_p, TOPAZ_HBM_BUF_DUMP_DFT, 0); |
| */ |
| } |
| arc_write_uncached_32(jiffp, jiffies); |
| arc_write_uncached_32(ownerp, (owner << 4) | TOPAZ_HBM_OWNER_FREE); |
| #endif |
| |
| if (ec != 1) { |
| arc_write_uncached_32(enqueuep, 1); |
| arc_write_uncached_32(freep, 0); |
| } |
| topaz_hbm_put_payload_aligned_bus(buf_bus, dest_pool); |
| } else { |
| arc_write_uncached_32(freep, fc); |
| } |
| } else { |
| HBM_STATS(HBM_CNT_INVALID_POOL, 1); |
| printk(KERN_CRIT "%s: unknown pool %hhd for buf_bus 0x%p\n", |
| __FUNCTION__, dest_pool, buf_bus); |
| } |
| } |
| EXPORT_SYMBOL(topaz_hbm_filter_txdone_buf); |
| |
| /* |
| * Safely return the buf. |
| * @pkt_bus needn't to be the pointer from the pool. It can be any location in the buffer. |
| */ |
| void topaz_hbm_release_buf_safe(void *const pkt_bus) |
| { |
| const int8_t dest_pool = topaz_hbm_payload_get_pool_bus(pkt_bus); |
| void *buf_bus = topaz_hbm_payload_store_align_bus(pkt_bus, dest_pool, 0); |
| unsigned long flags; |
| |
| local_irq_save(flags); |
| topaz_hbm_filter_txdone_buf(buf_bus); |
| local_irq_restore(flags); |
| } |
| EXPORT_SYMBOL(topaz_hbm_release_buf_safe); |
| |
| void topaz_hbm_filter_txdone_pool(void) |
| { |
| unsigned long flags; |
| void *buf_bus; |
| const int8_t src_pool = TOPAZ_HBM_EMAC_TX_DONE_POOL; |
| |
| const uint32_t mask = TOPAZ_HBM_EMAC_TX_DONE_COUNT - 1; |
| uint32_t wr_ptr; |
| uint32_t rd_ptr; |
| uint32_t wr_raw; |
| uint32_t rd_raw; |
| uint32_t i; |
| uint32_t count; |
| uint32_t full; |
| |
| if (unlikely(!g_pools_inited)) { |
| return; |
| } |
| |
| local_irq_save(flags); |
| |
| wr_raw = readl(TOPAZ_HBM_WR_PTR(src_pool)); |
| rd_raw = readl(TOPAZ_HBM_RD_PTR(src_pool)); |
| wr_ptr = wr_raw & mask; |
| rd_ptr = rd_raw & mask; |
| full = ((wr_raw != rd_raw) && (wr_ptr == rd_ptr)); |
| |
| for (count = 0, i = rd_ptr; ((i != wr_ptr) || full); i = (i + 1) & mask, count++) { |
| buf_bus = topaz_hbm_get_payload_bus(src_pool); |
| if (buf_bus != NULL) { |
| topaz_hbm_filter_txdone_buf(buf_bus); |
| } else if (printk_ratelimit()) { |
| printk(KERN_CRIT "%s: read NULL from pool %d\n", |
| __FUNCTION__, src_pool); |
| break; |
| } |
| full = 0; |
| } |
| #ifdef TOPAZ_EMAC_NULL_BUF_WR |
| if (topaz_emac_null_buf_del_cb) { |
| uint32_t n; |
| wr_ptr = readl(TOPAZ_HBM_WR_PTR(TOPAZ_HBM_BUF_EMAC_RX_POOL)); |
| rd_ptr = readl(TOPAZ_HBM_RD_PTR(TOPAZ_HBM_BUF_EMAC_RX_POOL)); |
| n = (wr_ptr - rd_ptr) % TOPAZ_HBM_BUF_EMAC_RX_COUNT; |
| if (n > HBM_UFLOW_RECOVER_TH) |
| topaz_emac_null_buf_del_cb(); |
| } |
| #endif |
| local_irq_restore(flags); |
| |
| if (unlikely(count > (TOPAZ_HBM_EMAC_TX_DONE_COUNT * 3 / 4))) { |
| if (printk_ratelimit()) |
| printk("Warning! %s count: %u\n", __FUNCTION__, count); |
| } |
| } |
| EXPORT_SYMBOL(topaz_hbm_filter_txdone_pool); |
| |
| static struct kmem_cache *shinfo_cache; |
| |
| static uint8_t *topaz_hbm_skb_allocator_payload_alloc(struct skb_shared_info **shinfo, |
| size_t size, gfp_t gfp_mask, int node) |
| { |
| uint8_t *data; |
| |
| size = SKB_DATA_ALIGN(size); |
| |
| *shinfo = kmem_cache_alloc(shinfo_cache, gfp_mask); |
| if (*shinfo == NULL) { |
| return NULL; |
| } |
| |
| if (size < topaz_hbm_pool_buf_max_size(TOPAZ_HBM_BUF_EMAC_RX_POOL)) { |
| data = topaz_hbm_get_payload_virt(TOPAZ_HBM_BUF_EMAC_RX_POOL); |
| } else { |
| data = kmalloc(size, gfp_mask); |
| } |
| |
| if (data == NULL) { |
| kmem_cache_free(shinfo_cache, *shinfo); |
| *shinfo = NULL; |
| } |
| |
| return data; |
| } |
| |
| static void topaz_hbm_skb_allocator_payload_free(struct sk_buff *skb) |
| { |
| void *buf_bus = (void *) virt_to_bus(skb->head); |
| const int8_t pool = topaz_hbm_payload_get_free_pool_bus(buf_bus); |
| |
| buf_bus = topaz_hbm_payload_store_align_bus(buf_bus, |
| topaz_hbm_payload_get_pool_bus(buf_bus), 0); |
| kmem_cache_free(shinfo_cache, skb_shinfo(skb)); |
| |
| if (topaz_hbm_pool_valid(pool)) { |
| if (!skb->hbm_no_free) { |
| unsigned long flags; |
| local_irq_save(flags); |
| |
| topaz_hbm_flush_skb_cache(skb); |
| topaz_hbm_filter_txdone_buf(buf_bus); |
| |
| local_irq_restore(flags); |
| } |
| } else { |
| kfree(skb->head); |
| } |
| |
| topaz_hbm_filter_txdone_pool(); |
| } |
| |
| const struct skb_allocator topaz_hbm_skb_allocator = { |
| .name = "topaz_hbm", |
| .skb_alloc = &skb_allocator_kmem_caches_skb_alloc, |
| .skb_free = &skb_allocator_kmem_caches_skb_free, |
| .payload_alloc = &topaz_hbm_skb_allocator_payload_alloc, |
| .payload_free = &topaz_hbm_skb_allocator_payload_free, |
| .max_size = 0, |
| }; |
| |
| #define QTN_HBM_MAX_FRAME_LEN 12000 /* 12000 is over maximum vht frame size, |
| and there is no rx frame whose size is over 12000 */ |
| struct sk_buff *_topaz_hbm_attach_skb(void *buf_virt, int8_t pool, int inv, uint8_t headroom |
| QTN_SKB_ALLOC_TRACE_ARGS) |
| { |
| struct sk_buff *skb; |
| struct skb_shared_info *shinfo; |
| uint32_t buf_size; |
| uint8_t *buf_head; |
| uintptr_t inv_start; |
| uintptr_t inv_end; |
| |
| if (unlikely(headroom > TOPAZ_HBM_PAYLOAD_HEADROOM)) { |
| printk(KERN_WARNING "specified headroom(%u) should be smaller than %u\n", |
| headroom, TOPAZ_HBM_PAYLOAD_HEADROOM); |
| return NULL; |
| } |
| |
| shinfo = kmem_cache_alloc(shinfo_cache, GFP_ATOMIC); |
| if (!shinfo) { |
| return NULL; |
| } |
| |
| skb = skb_allocator_kmem_caches_skb_alloc(GFP_ATOMIC, 0, -1); |
| if (!skb) { |
| kmem_cache_free(shinfo_cache, shinfo); |
| return NULL; |
| } |
| |
| /* |
| * TODO FIXME: Restrict the buffer size less than 12k, because we saw ping failed |
| * if we set skb buffer size as 17K |
| */ |
| buf_size = min((int)topaz_hbm_pool_buf_max_size(pool), QTN_HBM_MAX_FRAME_LEN); |
| buf_head = topaz_hbm_payload_store_align_virt(buf_virt, pool, 0) - headroom; |
| |
| /* invalidate all packet dcache before passing to the kernel */ |
| if (inv) |
| inv_dcache_sizerange_safe(buf_head, buf_size); |
| |
| inv_start = (uintptr_t) align_buf_cache(buf_head); |
| inv_end = align_val_up((uintptr_t) buf_head + buf_size, |
| dma_get_cache_alignment()); |
| inv_dcache_range(inv_start, inv_end); |
| |
| __alloc_skb_init(skb, shinfo, buf_head, |
| buf_size, 0, &topaz_hbm_skb_allocator |
| QTN_SKB_ALLOC_TRACE_ARGVARS); |
| skb_reserve(skb, ((uint8_t *) buf_virt) - buf_head); |
| |
| return skb; |
| } |
| EXPORT_SYMBOL(_topaz_hbm_attach_skb); |
| |
| /* |
| * Allocate a new buffer to hold the pkt. The new buffer is guranteed to be safe from wmac rx dma overrun. |
| * The original buffer is not used anymore. Caller should be responsible for freeing the buffer. |
| */ |
| struct sk_buff *topaz_hbm_attach_skb_quarantine(void *buf_virt, int pool, int len, uint8_t **whole_frm_hdr_p) |
| { |
| uint8_t *buf_head; |
| struct sk_buff *skb = NULL; |
| uint32_t prev_len; |
| uint32_t buf_size; |
| |
| KASSERT((pool == TOPAZ_HBM_BUF_WMAC_RX_POOL), ("buf quarantine is only for wmac rx pool, %d", pool)); |
| |
| buf_head = topaz_hbm_payload_store_align_virt(buf_virt, pool, 0); |
| if (hbm_buf_check_buf_magic(buf_head)) { |
| /* don't copy to new skb if it is aleady corrupted */ |
| HBM_STATS(HBM_CNT_QUARANTINE_CORRUPTED, 1); |
| return NULL; |
| } |
| |
| /* copy from buffer head in case mac header is needed */ |
| prev_len = (uint32_t)buf_virt - (uint32_t)buf_head; |
| buf_size = prev_len + len; |
| |
| skb = dev_alloc_skb(buf_size); |
| if (!skb) { |
| HBM_STATS(HBM_CNT_QUARANTINE_ALLOC_FAIL, 1); |
| return NULL; |
| } |
| |
| /* caller only invalidate this pkt, not entire buffer */ |
| inv_dcache_sizerange_safe(buf_head, prev_len); |
| if (whole_frm_hdr_p) |
| *whole_frm_hdr_p = skb->data; |
| memcpy(skb->data, buf_head, buf_size); |
| if (hbm_buf_check_buf_magic(buf_head)) { |
| /* if corruption happens during data copying */ |
| HBM_STATS(HBM_CNT_QUARANTINE_CORRUPTED, 1); |
| goto post_check_fail; |
| } |
| |
| /* reserve head space so that later caller's skb_put() covers the packet */ |
| skb_reserve(skb, prev_len); |
| HBM_STATS(HBM_CNT_QUARANTINE_OK, 1); |
| |
| /* |
| * Quarantine is done. now we are sure the data copy in skb is not corrupted and won't |
| */ |
| return skb; |
| |
| post_check_fail: |
| if (skb) |
| dev_kfree_skb(skb); |
| return NULL; |
| } |
| EXPORT_SYMBOL(topaz_hbm_attach_skb_quarantine); |
| |
| static int topaz_hbm_bufs_sort_need_swap(const uint32_t *buf0, const uint32_t *buf1) |
| { |
| int type; |
| int inv; |
| uint32_t v0; |
| uint32_t v1; |
| |
| type = topaz_hbm_dump_sort_type; |
| inv = 0; |
| if (topaz_hbm_dump_sort_type >= HBM_DUMP_SORT_ORDER_INV_BASE) { |
| type -= HBM_DUMP_SORT_ORDER_INV_BASE; |
| inv = 1; |
| } |
| |
| switch(type) { |
| case HBM_DUMP_SORT_ADDR: |
| v0 = (uint32_t)buf0; |
| v1 = (uint32_t)buf1; |
| break; |
| case HBM_DUMP_SORT_JIFF: |
| v0 = jiffies - arc_read_uncached_32(topaz_hbm_buf_get_meta(buf0) - HBM_HR_OFFSET_FREE_JIFF); |
| v1 = jiffies - arc_read_uncached_32(topaz_hbm_buf_get_meta(buf1) - HBM_HR_OFFSET_FREE_JIFF); |
| break; |
| case HBM_DUMP_SORT_BAD_MAGIC: |
| v0 = (arc_read_uncached_32(buf0 - HBM_HR_OFFSET_MAGIC) == TOPAZ_HBM_BUF_GUARD_MAGIC); |
| v1 = (arc_read_uncached_32(buf1 - HBM_HR_OFFSET_MAGIC) == TOPAZ_HBM_BUF_GUARD_MAGIC); |
| break; |
| default: |
| return 0; |
| break; |
| } |
| |
| return (inv ? (v1 > v0) : (v0 > v1)); |
| } |
| |
| static void topaz_hbm_bufs_sort(int pool, int pool_size) |
| { |
| int i; |
| int j; |
| uint32_t *buf0; |
| uint32_t *buf1; |
| uint32_t *buf; |
| int swapped; |
| |
| memset(topaz_hbm_dump_bufs_sorted, 0, sizeof(topaz_hbm_dump_bufs_sorted)); |
| for (i = 0; i < pool_size; i++) { |
| topaz_hbm_dump_bufs_sorted[i] = (uint32_t*) |
| topaz_hbm_payload_store_align_from_index(pool, i); |
| } |
| |
| /* bubble sort */ |
| for (i = 0; i < (pool_size - 1); i++) { |
| swapped = 0; |
| for (j = 0; j < (pool_size - i - 1); j++) { |
| buf0 = topaz_hbm_dump_bufs_sorted[j]; |
| buf1 = topaz_hbm_dump_bufs_sorted[j + 1]; |
| if (topaz_hbm_bufs_sort_need_swap(buf0, buf1)) { |
| buf = buf0; |
| topaz_hbm_dump_bufs_sorted[i] = buf1; |
| topaz_hbm_dump_bufs_sorted[j] = buf; |
| swapped = 1; |
| } |
| } |
| if (!swapped) |
| break; |
| } |
| |
| topaz_hbm_dumped_num = 0; |
| } |
| |
| static int topaz_hbm_buf_in_range(const uint32_t *buf) |
| { |
| int type; |
| uint32_t v; |
| |
| type = topaz_hbm_dump_sort_type; |
| if (topaz_hbm_dump_sort_type >= HBM_DUMP_SORT_ORDER_INV_BASE) { |
| type -= HBM_DUMP_SORT_ORDER_INV_BASE; |
| } |
| |
| switch(type) { |
| case HBM_DUMP_SORT_ADDR: |
| v = (uint32_t)buf; |
| break; |
| case HBM_DUMP_SORT_JIFF: |
| v = jiffies - arc_read_uncached_32(topaz_hbm_buf_get_meta(buf) - HBM_HR_OFFSET_FREE_JIFF); |
| break; |
| case HBM_DUMP_SORT_BAD_MAGIC: |
| v = (arc_read_uncached_32(buf - HBM_HR_OFFSET_MAGIC) == TOPAZ_HBM_BUF_GUARD_MAGIC); |
| break; |
| default: |
| return 0; |
| break; |
| } |
| |
| return ((topaz_hbm_dump_sort_range_min <= v) && (v <= topaz_hbm_dump_sort_range_max)); |
| } |
| |
| static void *topaz_hbm_bufs_emac_seq_start(struct seq_file *sfile, loff_t *pos) |
| { |
| if (*pos > (TOPAZ_HBM_POOL_SIZE_MAX - 1)) |
| return NULL; |
| |
| if (*pos == 0) |
| topaz_hbm_bufs_sort(TOPAZ_HBM_BUF_EMAC_RX_POOL, TOPAZ_HBM_BUF_EMAC_RX_COUNT); |
| |
| return topaz_hbm_dump_bufs_sorted[*pos]; |
| } |
| |
| static void *topaz_hbm_bufs_wmac_seq_start(struct seq_file *sfile, loff_t *pos) |
| { |
| if (*pos > (TOPAZ_HBM_POOL_SIZE_MAX - 1)) |
| return NULL; |
| |
| if (*pos == 0) |
| topaz_hbm_bufs_sort(TOPAZ_HBM_BUF_WMAC_RX_POOL, TOPAZ_HBM_BUF_WMAC_RX_COUNT); |
| |
| return topaz_hbm_dump_bufs_sorted[*pos]; |
| } |
| |
| static void* topaz_hbm_bufs_seq_next(struct seq_file *sfile, void *v, loff_t *pos) |
| { |
| if (*pos > (TOPAZ_HBM_POOL_SIZE_MAX - 1)) |
| return NULL; |
| |
| *pos += 1; |
| |
| return topaz_hbm_dump_bufs_sorted[*pos]; |
| } |
| |
| static void topaz_hbm_bufs_seq_stop(struct seq_file *sfile, void *v) |
| { |
| } |
| |
| static int topaz_hbm_bufs_seq_show(struct seq_file *sfile, void *v) |
| { |
| const uint32_t *_p = v; |
| const uint32_t *_m = topaz_hbm_buf_get_meta(_p); |
| const uint32_t *enqueuep = _m - HBM_HR_OFFSET_ENQ_CNT; |
| const uint32_t *freep = _m - HBM_HR_OFFSET_FREE_CNT; |
| const uint32_t *jiffp = _m - HBM_HR_OFFSET_FREE_JIFF; |
| const uint32_t *ownerp = _m - HBM_HR_OFFSET_OWNER; |
| const uint32_t *sizep = _m - HBM_HR_OFFSET_SIZE; |
| const uint32_t *magicp = _p - HBM_HR_OFFSET_MAGIC; |
| const uint32_t ec = arc_read_uncached_32(enqueuep); |
| const uint32_t fc = arc_read_uncached_32(freep); |
| const uint32_t jc = arc_read_uncached_32(jiffp); |
| const uint32_t oc = arc_read_uncached_32(ownerp); |
| const uint32_t sz = arc_read_uncached_32(sizep); |
| const uint32_t magic = arc_read_uncached_32(magicp); |
| const uint8_t *d; |
| int dump_bytes; |
| int i; |
| uint8_t *tail; |
| int tail_bytes; |
| uint32_t whole_size; |
| uint32_t payload_size; |
| int pool; |
| uint32_t idx; |
| |
| if (!topaz_hbm_buf_in_range(_p)) { |
| return 0; |
| } |
| |
| if (topaz_hbm_dumped_num++ >= topaz_hbm_dump_num) { |
| return 0; |
| } |
| |
| pool = topaz_hbm_buf_identify_buf_virt(v, &whole_size, &idx); |
| if (pool < 0) { |
| seq_printf(sfile, "invalid hbm buffer %x\n", (unsigned int)v); |
| return 0; |
| } |
| payload_size = whole_size - TOPAZ_HBM_PAYLOAD_HEADROOM; |
| |
| dump_bytes = (topaz_hbm_dump_len == TOPAZ_HBM_BUF_DUMP_MAX) ? payload_size : topaz_hbm_dump_len; |
| |
| d = v; |
| inv_dcache_sizerange_safe(v, dump_bytes); |
| seq_printf(sfile, "%p ec %u fp %u own %08x size %u j %u (%u s ago) mg %x\n", |
| v, ec, fc, oc, sz, jc, (((uint32_t) jiffies) - jc) / HZ, magic); |
| for (i = 0; i < dump_bytes; ) { |
| if (!(i % 32)) |
| seq_printf(sfile, "%08x ", (i - i % 32)); |
| ++i; |
| seq_printf(sfile, "%02x%s", *d++, (i % 32) == 0 ? "\n" : " "); |
| } |
| |
| if (topaz_hbm_dump_taillen) { |
| seq_printf(sfile, "\n"); |
| tail_bytes = topaz_hbm_dump_taillen; |
| tail = (uint8_t*)((uint32_t)v + payload_size - tail_bytes); |
| inv_dcache_sizerange_safe(tail, tail_bytes); |
| seq_printf(sfile, "%p tail %p\n", v, tail); |
| for (i = 0; i < tail_bytes; ) { |
| if (!(i % 32)) |
| seq_printf(sfile, "%08x ", (i - i % 32)); |
| ++i; |
| seq_printf(sfile, "%02x%s", *tail++, (i % 32) == 0 ? "\n" : " "); |
| } |
| } |
| seq_printf(sfile, "\n"); |
| |
| return 0; |
| } |
| |
| static struct seq_operations topaz_hbm_bufs_emac_seq_ops = { |
| .start = topaz_hbm_bufs_emac_seq_start, |
| .next = topaz_hbm_bufs_seq_next, |
| .stop = topaz_hbm_bufs_seq_stop, |
| .show = topaz_hbm_bufs_seq_show |
| }; |
| |
| static struct seq_operations topaz_hbm_bufs_wmac_seq_ops = { |
| .start = topaz_hbm_bufs_wmac_seq_start, |
| .next = topaz_hbm_bufs_seq_next, |
| .stop = topaz_hbm_bufs_seq_stop, |
| .show = topaz_hbm_bufs_seq_show |
| }; |
| |
| static int topaz_hbm_bufs_emac_proc_open(struct inode *inode, struct file *file) |
| { |
| return seq_open(file, &topaz_hbm_bufs_emac_seq_ops); |
| } |
| |
| static int topaz_hbm_bufs_wmac_proc_open(struct inode *inode, struct file *file) |
| { |
| return seq_open(file, &topaz_hbm_bufs_wmac_seq_ops); |
| } |
| |
| static struct file_operations topaz_hbm_bufs_emac_proc_ops = { |
| .owner = THIS_MODULE, |
| .open = topaz_hbm_bufs_emac_proc_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release |
| }; |
| |
| static struct file_operations topaz_hbm_bufs_wmac_proc_ops = { |
| .owner = THIS_MODULE, |
| .open = topaz_hbm_bufs_wmac_proc_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release |
| }; |
| |
| static inline int hbm_if_split_words(char **words, char *str) |
| { |
| int word_count = 0; |
| |
| /* skip leading space */ |
| while (str && *str && isspace(*str)) { |
| str++; |
| } |
| |
| while (str && *str) { |
| words[word_count++] = str; |
| |
| /* skip this word */ |
| while (str && *str && !isspace(*str)) { |
| str++; |
| } |
| |
| /* replace spaces with NULL */ |
| while (str && *str && isspace(*str)) { |
| *str = 0; |
| str++; |
| } |
| } |
| |
| return word_count; |
| } |
| |
| static int hbm_if_cmd_dumpctl(char **words, uint8_t word_count) |
| { |
| int idx = 1; |
| |
| if (word_count >= (idx + 1)) |
| sscanf(words[idx++], "%u", &topaz_hbm_dump_sort_type); |
| if (word_count >= (idx + 1)) |
| sscanf(words[idx++], "0x%x", &topaz_hbm_dump_sort_range_min); |
| if (word_count >= (idx + 1)) |
| sscanf(words[idx++], "0x%x", &topaz_hbm_dump_sort_range_max); |
| if (word_count >= (idx + 1)) |
| sscanf(words[idx++], "%u", &topaz_hbm_dump_num); |
| if (word_count >= (idx + 1)) |
| sscanf(words[idx++], "%u", &topaz_hbm_dump_len); |
| if (word_count >= (idx + 1)) |
| sscanf(words[idx++], "%u", &topaz_hbm_dump_taillen); |
| |
| printk("hbm_if set dump ctl: sort_type %u sort_range [0x%x 0x%x] num %u len %u %u\n", |
| topaz_hbm_dump_sort_type, |
| topaz_hbm_dump_sort_range_min, |
| topaz_hbm_dump_sort_range_max, |
| topaz_hbm_dump_num, |
| topaz_hbm_dump_len, |
| topaz_hbm_dump_taillen |
| ); |
| return 0; |
| } |
| |
| static int hbm_if_cmd_show_stats(char **words, uint8_t word_count) |
| { |
| int i; |
| |
| printk("HBM stats:\n"); |
| |
| for (i = 0; i < HBM_CNT_NUM; i++) { |
| printk("%s = %u\n", topaz_hbm_stats_names[i], topaz_hbm_stats[i]); |
| } |
| |
| return 0; |
| } |
| |
| /* Apply user command. |
| * User command can control the HBM interface. |
| * @param cmd_num: command number |
| * @param words: the split words without spaces from the user space console interface |
| * @param word_count: number of words after split |
| * @return: status indication |
| */ |
| static int hbm_if_apply_user_command(hbm_if_usr_cmd cmd_num, char **words, uint8_t word_count) |
| { |
| int rc = -EINVAL; |
| |
| if ((word_count == 0) || (!words)) { |
| goto cmd_failure; |
| } |
| |
| switch(cmd_num) { |
| case HBM_IF_CMD_DUMPCTL: |
| rc = hbm_if_cmd_dumpctl(words, word_count); |
| break; |
| case HBM_IF_CMD_STATS: |
| rc = hbm_if_cmd_show_stats(words, word_count); |
| break; |
| default: |
| goto cmd_failure; |
| break; |
| } |
| |
| if (rc < 0) { |
| goto cmd_failure; |
| } |
| |
| return 1; |
| |
| cmd_failure: |
| if (words) |
| printk(KERN_INFO "Failed to parse command:%s, word count:%d\n", *words, word_count); |
| else |
| printk(KERN_INFO "Failed to parse command:(NULL)\n"); |
| |
| return -EPERM; |
| } |
| |
| static int hbm_if_write_proc(struct file *file, const char __user *buffer, |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| size_t count, loff_t *ppos) |
| #else |
| unsigned long count, void *_unused) |
| #endif |
| { |
| char *cmd; |
| int rc, i; |
| char **words; |
| uint8_t word_count; |
| hbm_if_usr_cmd cmd_num = 0; |
| |
| cmd = kmalloc(count, GFP_KERNEL); |
| words = kmalloc(count * sizeof(char *) / 2, GFP_KERNEL); |
| if (!cmd || !words) { |
| rc = -ENOMEM; |
| goto out; |
| } |
| |
| if (copy_from_user(cmd, buffer, count)) { |
| rc = -EFAULT; |
| goto out; |
| } |
| |
| /* Set null at last byte, note that count already gives +1 byte count*/ |
| cmd[count - 1] = '\0'; |
| |
| word_count = hbm_if_split_words(words, cmd); |
| for (i = 0; i < HBM_IF_MAX_CMD; i++, cmd_num++) { |
| /* Extract command from first word */ |
| if (strcmp(words[0], str_cmd[i]) == 0) { |
| printk(KERN_INFO"HBM user command:%s \n", str_cmd[i]); |
| break; |
| } |
| } |
| |
| /* Exclude softirqs whilst manipulating forwarding table */ |
| local_bh_disable(); |
| |
| rc = hbm_if_apply_user_command(cmd_num, words, word_count); |
| |
| local_bh_enable(); |
| |
| out: |
| if (cmd) { |
| kfree(cmd); |
| } |
| if (words) { |
| kfree(words); |
| } |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| *ppos += count; |
| #endif |
| return count; |
| } |
| |
| static const struct file_operations hbm_if_fops = { |
| .write = hbm_if_write_proc, |
| }; |
| static inline uint32_t hbm_get_rel_cnt(int pool) |
| { |
| int master; |
| uint32_t rel = 0; |
| |
| for (master = 0; master < TOPAZ_HBM_MASTER_COUNT; ++master) |
| rel += readl(TOPAZ_HBM_POOL_RELEASE_CNT(master, pool)); |
| |
| return rel; |
| } |
| |
| static int topaz_hbm_pool_poll_stat(int pool, struct hbm_pool_cnt *ps) |
| { |
| uint32_t free; |
| uint32_t fdelt; |
| int rc; |
| |
| free = hbm_get_rel_cnt(pool); |
| fdelt = free - ps->prev_release_cnt; |
| ps->prev_release_cnt = free; |
| |
| if ((topaz_hbm_pool_available(pool) < HBM_BUF_MINIMUM_AVAIL_NUM) && |
| (fdelt < HBM_BUF_MINIMUM_REL_NUM)) { |
| ps->pool_depleted_cnt++; |
| rc = -1; |
| } else { |
| ps->pool_depleted_cnt = 0; |
| rc = 0; |
| } |
| |
| return rc; |
| } |
| |
| void topaz_hbm_monitor(unsigned long data) |
| { |
| struct topaz_hbm_mnt *hm = (struct topaz_hbm_mnt *)data; |
| uint32_t uf; |
| int rc; |
| unsigned long intval; |
| |
| intval = HBM_BUF_POLL_L_INTRVAL; |
| if (!hm->unflow_flag) { |
| uf = readl(TOPAZ_HBM_UNDERFLOW_CNT); |
| if (uf - hm->prev_unflow_cnt) { |
| hm->unflow_flag = 1; |
| hm->prev_unflow_cnt = uf; |
| } else { |
| goto exit; |
| } |
| } |
| |
| rc = topaz_hbm_pool_poll_stat(TOPAZ_HBM_BUF_WMAC_RX_POOL, &hm->wmac_pl); |
| rc += topaz_hbm_pool_poll_stat(TOPAZ_HBM_BUF_EMAC_RX_POOL, &hm->emac_pl); |
| |
| if (rc == 0) { |
| hm->unflow_flag = 0; |
| } else { |
| if ((hm->wmac_pl.pool_depleted_cnt > HBM_BUF_DEPLETION_TH) || |
| (hm->emac_pl.pool_depleted_cnt > HBM_BUF_DEPLETION_TH)) { |
| panic("HBM pool is depleted, wmac pool:%u, emac rx pool:%u\n", |
| topaz_hbm_pool_available(TOPAZ_HBM_BUF_WMAC_RX_POOL), |
| topaz_hbm_pool_available(TOPAZ_HBM_BUF_EMAC_RX_POOL)); |
| } |
| intval = HBM_BUF_POLL_S_INTRVAL; |
| } |
| exit: |
| mod_timer(&hbm_timer, jiffies + intval); |
| } |
| |
| static int __init topaz_hbm_bufs_init(void) |
| { |
| struct topaz_hbm_mnt *hm; |
| struct proc_dir_entry *e; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| e = proc_create("hbm_bufs_emac", 0x444, NULL, &topaz_hbm_bufs_emac_proc_ops); |
| if (!e) |
| goto error4; |
| |
| e = proc_create("hbm_bufs_wmac", 0x444, NULL, &topaz_hbm_bufs_wmac_proc_ops); |
| if (!e) |
| goto error3; |
| |
| e = proc_create(TOPAZ_HBM_IF_PROC_NAME, 0x444, NULL, &hbm_if_fops); |
| if (!e) |
| goto error2; |
| |
| hm = (struct topaz_hbm_mnt *)kzalloc(sizeof(*hm), GFP_KERNEL); |
| if (!hm) |
| goto error1; |
| |
| |
| #else |
| if ((e = create_proc_entry("hbm_bufs_emac", 0, NULL)) != NULL) { |
| e->proc_fops = &topaz_hbm_bufs_emac_proc_ops; |
| } |
| |
| if ((e = create_proc_entry("hbm_bufs_wmac", 0, NULL)) != NULL) { |
| e->proc_fops = &topaz_hbm_bufs_wmac_proc_ops; |
| } |
| |
| struct proc_dir_entry *entry = create_proc_entry(TOPAZ_HBM_IF_PROC_NAME, 0600, NULL); |
| if (entry) { |
| entry->write_proc = hbm_if_write_proc; |
| entry->read_proc = NULL; |
| } |
| |
| hm = (struct topaz_hbm_mnt *)kzalloc(sizeof(*hm), GFP_KERNEL); |
| if (!hm) { |
| printk(KERN_ERR"%s: fail to allocate hm", __func__); |
| return -1; |
| } |
| #endif |
| |
| init_timer(&hbm_timer); |
| hbm_timer.data = (unsigned long)hm; |
| hbm_timer.function = &topaz_hbm_monitor; |
| mod_timer(&hbm_timer, jiffies + HBM_BUF_POLL_L_INTRVAL); |
| |
| return 0; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| error1: |
| remove_proc_entry(TOPAZ_HBM_IF_PROC_NAME, 0); |
| error2: |
| remove_proc_entry("hbm_bufs_wmac", 0); |
| error3: |
| remove_proc_entry("hbm_bufs_emac", 0); |
| error4: |
| return -1; |
| #endif |
| } |
| |
| static void __exit topaz_hbm_bufs_exit(void) |
| { |
| remove_proc_entry("hbm_bufs_wmac", 0); |
| remove_proc_entry("hbm_bufs_emac", 0); |
| remove_proc_entry(TOPAZ_HBM_IF_PROC_NAME, NULL); |
| |
| del_timer(&hbm_timer); |
| if (hbm_timer.data) |
| kfree((void *)hbm_timer.data); |
| } |
| |
| static int __init topaz_hbm_module_init(void) |
| { |
| COMPILE_TIME_ASSERT(TOPAZ_HBM_BUF_META_SIZE >= (HBM_HR_OFFSET_MAX * 4)); |
| |
| topaz_hbm_init_payload_pools(); |
| |
| shinfo_cache = kmem_cache_create("topaz_skb_shinfo_cache", |
| sizeof(struct skb_shared_info), |
| 0, |
| SLAB_HWCACHE_ALIGN | SLAB_PANIC, |
| NULL); |
| |
| skb_allocator_register(TOPAZ_HBM_SKB_ALLOCATOR, &topaz_hbm_skb_allocator, 0); |
| |
| topaz_hbm_stat_init(); |
| topaz_hbm_bufs_init(); |
| |
| return 0; |
| } |
| module_init(topaz_hbm_module_init) |
| |
| static void __exit topaz_hbm_module_exit(void) |
| { |
| topaz_hbm_stat_exit(); |
| topaz_hbm_bufs_exit(); |
| } |
| module_exit(topaz_hbm_module_exit) |
| |
| |