/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2021, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /*$FreeBSD$*/ /** * @file ice_osdep.c * @brief Functions used to implement OS compatibility layer * * Contains functions used by ice_osdep.h to implement the OS compatibility * layer used by some of the hardware files. Specifically, it is for the bits * of OS compatibility which don't make sense as macros or inline functions. */ #include "ice_common.h" #include "ice_iflib.h" #include #include /** * @var M_ICE_OSDEP * @brief OS compatibility layer allocation type * * malloc(9) allocation type used by the OS compatibility layer for * distinguishing allocations by this layer from those of the rest of the * driver. */ MALLOC_DEFINE(M_ICE_OSDEP, "ice-osdep", "Intel(R) 100Gb Network Driver osdep allocations"); /** * @var ice_lock_count * @brief Global count of # of ice_lock mutexes initialized * * A global count of the total number of times that ice_init_lock has been * called. This is used to generate unique lock names for each ice_lock, to * aid in witness lock checking. */ u16 ice_lock_count = 0; static void ice_dmamap_cb(void *arg, bus_dma_segment_t * segs, int __unused nseg, int error); /** * ice_hw_to_dev - Given a hw private struct, find the associated device_t * @hw: the hardware private structure * * Given a hw structure pointer, lookup the softc and extract the device * pointer. Assumes that hw is embedded within the ice_softc, instead of being * allocated separately, so that __containerof math will work. * * This can't be defined in ice_osdep.h as it depends on the complete * definition of struct ice_softc. That can't be easily included in * ice_osdep.h without creating circular header dependencies. */ device_t ice_hw_to_dev(struct ice_hw *hw) { struct ice_softc *sc = __containerof(hw, struct ice_softc, hw); return sc->dev; } /** * ice_debug - Log a debug message if the type is enabled * @hw: device private hardware structure * @mask: the debug message type * @fmt: printf format specifier * * Check if hw->debug_mask has enabled the given message type. If so, log the * message to the console using vprintf. Mimic the output of device_printf by * using device_print_prettyname(). */ void ice_debug(struct ice_hw *hw, uint64_t mask, char *fmt, ...) { device_t dev = ice_hw_to_dev(hw); va_list args; if (!(mask & hw->debug_mask)) return; device_print_prettyname(dev); va_start(args, fmt); vprintf(fmt, args); va_end(args); } /** * ice_debug_array - Format and print an array of values to the console * @hw: private hardware structure * @mask: the debug message type * @rowsize: preferred number of rows to use * @groupsize: preferred size in bytes to print each chunk * @buf: the array buffer to print * @len: size of the array buffer * * Format the given array as a series of uint8_t values with hexadecimal * notation and log the contents to the console log. * * TODO: Currently only supports a group size of 1, due to the way hexdump is * implemented. */ void ice_debug_array(struct ice_hw *hw, uint64_t mask, uint32_t rowsize, uint32_t __unused groupsize, uint8_t *buf, size_t len) { device_t dev = ice_hw_to_dev(hw); char prettyname[20]; if (!(mask & hw->debug_mask)) return; /* Format the device header to a string */ snprintf(prettyname, sizeof(prettyname), "%s: ", device_get_nameunit(dev)); /* Make sure the row-size isn't too large */ if (rowsize > 0xFF) rowsize = 0xFF; hexdump(buf, len, prettyname, HD_OMIT_CHARS | rowsize); } /** * rd32 - Read a 32bit hardware register value * @hw: the private hardware structure * @reg: register address to read * * Read the specified 32bit register value from BAR0 and return its contents. */ uint32_t rd32(struct ice_hw *hw, uint32_t reg) { struct ice_softc *sc = __containerof(hw, struct ice_softc, hw); return bus_space_read_4(sc->bar0.tag, sc->bar0.handle, reg); } /** * rd64 - Read a 64bit hardware register value * @hw: the private hardware structure * @reg: register address to read * * Read the specified 64bit register value from BAR0 and return its contents. * * @pre For 32-bit builds, assumes that the 64bit register read can be * safely broken up into two 32-bit register reads. */ uint64_t rd64(struct ice_hw *hw, uint32_t reg) { struct ice_softc *sc = __containerof(hw, struct ice_softc, hw); uint64_t data; #ifdef __amd64__ data = bus_space_read_8(sc->bar0.tag, sc->bar0.handle, reg); #else /* * bus_space_read_8 isn't supported on 32bit platforms, so we fall * back to using two bus_space_read_4 calls. */ data = bus_space_read_4(sc->bar0.tag, sc->bar0.handle, reg); data |= ((uint64_t)bus_space_read_4(sc->bar0.tag, sc->bar0.handle, reg + 4)) << 32; #endif return data; } /** * wr32 - Write a 32bit hardware register * @hw: the private hardware structure * @reg: the register address to write to * @val: the 32bit value to write * * Write the specified 32bit value to a register address in BAR0. */ void wr32(struct ice_hw *hw, uint32_t reg, uint32_t val) { struct ice_softc *sc = __containerof(hw, struct ice_softc, hw); bus_space_write_4(sc->bar0.tag, sc->bar0.handle, reg, val); } /** * wr64 - Write a 64bit hardware register * @hw: the private hardware structure * @reg: the register address to write to * @val: the 64bit value to write * * Write the specified 64bit value to a register address in BAR0. * * @pre For 32-bit builds, assumes that the 64bit register write can be safely * broken up into two 32-bit register writes. */ void wr64(struct ice_hw *hw, uint32_t reg, uint64_t val) { struct ice_softc *sc = __containerof(hw, struct ice_softc, hw); #ifdef __amd64__ bus_space_write_8(sc->bar0.tag, sc->bar0.handle, reg, val); #else uint32_t lo_val, hi_val; /* * bus_space_write_8 isn't supported on 32bit platforms, so we fall * back to using two bus_space_write_4 calls. */ lo_val = (uint32_t)val; hi_val = (uint32_t)(val >> 32); bus_space_write_4(sc->bar0.tag, sc->bar0.handle, reg, lo_val); bus_space_write_4(sc->bar0.tag, sc->bar0.handle, reg + 4, hi_val); #endif } /** * ice_usec_delay - Delay for the specified number of microseconds * @time: microseconds to delay * @sleep: if true, sleep where possible * * If sleep is true, and if the current thread is allowed to sleep, pause so * that another thread can execute. Otherwise, use DELAY to spin the thread * instead. */ void ice_usec_delay(uint32_t time, bool sleep) { if (sleep && THREAD_CAN_SLEEP()) pause("ice_usec_delay", USEC_2_TICKS(time)); else DELAY(time); } /** * ice_msec_delay - Delay for the specified number of milliseconds * @time: milliseconds to delay * @sleep: if true, sleep where possible * * If sleep is true, and if the current thread is allowed to sleep, pause so * that another thread can execute. Otherwise, use DELAY to spin the thread * instead. */ void ice_msec_delay(uint32_t time, bool sleep) { if (sleep && THREAD_CAN_SLEEP()) pause("ice_msec_delay", MSEC_2_TICKS(time)); else DELAY(time * 1000); } /** * ice_msec_pause - pause (sleep) the thread for a time in milliseconds * @time: milliseconds to sleep * * Wrapper for ice_msec_delay with sleep set to true. */ void ice_msec_pause(uint32_t time) { ice_msec_delay(time, true); } /** * ice_msec_spin - Spin the thread for a time in milliseconds * @time: milliseconds to delay * * Wrapper for ice_msec_delay with sleep sent to false. */ void ice_msec_spin(uint32_t time) { ice_msec_delay(time, false); } /******************************************************************** * Manage DMA'able memory. *******************************************************************/ /** * ice_dmamap_cb - Callback function DMA maps * @arg: pointer to return the segment address * @segs: the segments array * @nseg: number of segments in the array * @error: error code * * Callback used by the bus DMA code to obtain the segment address. */ static void ice_dmamap_cb(void *arg, bus_dma_segment_t * segs, int __unused nseg, int error) { if (error) return; *(bus_addr_t *) arg = segs->ds_addr; return; } /** * ice_alloc_dma_mem - Request OS to allocate DMA memory * @hw: private hardware structure * @mem: structure defining the DMA memory request * @size: the allocation size * * Allocates some memory for DMA use. Use the FreeBSD bus DMA interface to * track this memory using a bus DMA tag and map. * * Returns a pointer to the DMA memory address. */ void * ice_alloc_dma_mem(struct ice_hw *hw, struct ice_dma_mem *mem, u64 size) { device_t dev = ice_hw_to_dev(hw); int err; err = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ size, /* maxsize */ 1, /* nsegments */ size, /* maxsegsz */ BUS_DMA_ALLOCNOW, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &mem->tag); if (err != 0) { device_printf(dev, "ice_alloc_dma: bus_dma_tag_create failed, " "error %s\n", ice_err_str(err)); goto fail_0; } err = bus_dmamem_alloc(mem->tag, (void **)&mem->va, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &mem->map); if (err != 0) { device_printf(dev, "ice_alloc_dma: bus_dmamem_alloc failed, " "error %s\n", ice_err_str(err)); goto fail_1; } err = bus_dmamap_load(mem->tag, mem->map, mem->va, size, ice_dmamap_cb, &mem->pa, BUS_DMA_NOWAIT); if (err != 0) { device_printf(dev, "ice_alloc_dma: bus_dmamap_load failed, " "error %s\n", ice_err_str(err)); goto fail_2; } mem->size = size; bus_dmamap_sync(mem->tag, mem->map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); return (mem->va); fail_2: bus_dmamem_free(mem->tag, mem->va, mem->map); fail_1: bus_dma_tag_destroy(mem->tag); fail_0: mem->map = NULL; mem->tag = NULL; return (NULL); } /** * ice_free_dma_mem - Free DMA memory allocated by ice_alloc_dma_mem * @hw: the hardware private structure * @mem: DMA memory to free * * Release the bus DMA tag and map, and free the DMA memory associated with * it. */ void ice_free_dma_mem(struct ice_hw __unused *hw, struct ice_dma_mem *mem) { bus_dmamap_sync(mem->tag, mem->map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(mem->tag, mem->map); bus_dmamem_free(mem->tag, mem->va, mem->map); bus_dma_tag_destroy(mem->tag); mem->map = NULL; mem->tag = NULL; }