Aaron Williams | 16c50d6 | 2021-05-10 13:45:15 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright (C) 2018-2022 Marvell International Ltd. |
| 4 | * |
| 5 | * Support functions for managing command queues used for |
| 6 | * various hardware blocks. |
| 7 | */ |
| 8 | |
| 9 | #include <errno.h> |
| 10 | #include <log.h> |
| 11 | #include <time.h> |
| 12 | #include <linux/delay.h> |
| 13 | |
| 14 | #include <mach/cvmx-regs.h> |
| 15 | #include <mach/cvmx-csr.h> |
| 16 | #include <mach/cvmx-bootmem.h> |
| 17 | #include <mach/octeon-model.h> |
| 18 | #include <mach/cvmx-fuse.h> |
| 19 | #include <mach/octeon-feature.h> |
| 20 | #include <mach/cvmx-qlm.h> |
| 21 | #include <mach/octeon_qlm.h> |
| 22 | #include <mach/cvmx-pcie.h> |
| 23 | #include <mach/cvmx-coremask.h> |
| 24 | |
| 25 | #include <mach/cvmx-fpa.h> |
| 26 | #include <mach/cvmx-cmd-queue.h> |
| 27 | |
| 28 | #include <mach/cvmx-agl-defs.h> |
| 29 | #include <mach/cvmx-bgxx-defs.h> |
| 30 | #include <mach/cvmx-ciu-defs.h> |
| 31 | #include <mach/cvmx-gmxx-defs.h> |
| 32 | #include <mach/cvmx-gserx-defs.h> |
| 33 | #include <mach/cvmx-ilk-defs.h> |
| 34 | #include <mach/cvmx-ipd-defs.h> |
| 35 | #include <mach/cvmx-pcsx-defs.h> |
| 36 | #include <mach/cvmx-pcsxx-defs.h> |
| 37 | #include <mach/cvmx-pki-defs.h> |
| 38 | #include <mach/cvmx-pko-defs.h> |
| 39 | #include <mach/cvmx-xcv-defs.h> |
| 40 | |
| 41 | #include <mach/cvmx-hwpko.h> |
| 42 | #include <mach/cvmx-ilk.h> |
| 43 | #include <mach/cvmx-pki.h> |
| 44 | #include <mach/cvmx-pko3.h> |
| 45 | #include <mach/cvmx-pko3-queue.h> |
| 46 | #include <mach/cvmx-pko3-resources.h> |
| 47 | |
| 48 | #include <mach/cvmx-helper.h> |
| 49 | #include <mach/cvmx-helper-board.h> |
| 50 | #include <mach/cvmx-helper-cfg.h> |
| 51 | |
| 52 | #include <mach/cvmx-helper-bgx.h> |
| 53 | #include <mach/cvmx-helper-cfg.h> |
| 54 | #include <mach/cvmx-helper-util.h> |
| 55 | #include <mach/cvmx-helper-pki.h> |
| 56 | |
| 57 | #include <mach/cvmx-helper-util.h> |
| 58 | #include <mach/cvmx-dpi-defs.h> |
| 59 | #include <mach/cvmx-npei-defs.h> |
| 60 | #include <mach/cvmx-pexp-defs.h> |
| 61 | |
| 62 | /** |
| 63 | * This application uses this pointer to access the global queue |
| 64 | * state. It points to a bootmem named block. |
| 65 | */ |
| 66 | __cvmx_cmd_queue_all_state_t *__cvmx_cmd_queue_state_ptrs[CVMX_MAX_NODES]; |
| 67 | |
| 68 | /** |
| 69 | * @INTERNAL |
| 70 | * Initialize the Global queue state pointer. |
| 71 | * |
| 72 | * @return CVMX_CMD_QUEUE_SUCCESS or a failure code |
| 73 | */ |
| 74 | cvmx_cmd_queue_result_t __cvmx_cmd_queue_init_state_ptr(unsigned int node) |
| 75 | { |
| 76 | const char *alloc_name = "cvmx_cmd_queues\0\0"; |
| 77 | char s[4] = "_0"; |
| 78 | const struct cvmx_bootmem_named_block_desc *block_desc = NULL; |
| 79 | unsigned int size; |
| 80 | u64 paddr_min = 0, paddr_max = 0; |
| 81 | void *ptr; |
| 82 | |
| 83 | if (cvmx_likely(__cvmx_cmd_queue_state_ptrs[node])) |
| 84 | return CVMX_CMD_QUEUE_SUCCESS; |
| 85 | |
| 86 | /* Add node# to block name */ |
| 87 | if (node > 0) { |
| 88 | s[1] += node; |
| 89 | strcat((char *)alloc_name, s); |
| 90 | } |
| 91 | |
| 92 | /* Find the named block in case it has been created already */ |
| 93 | block_desc = cvmx_bootmem_find_named_block(alloc_name); |
| 94 | if (block_desc) { |
| 95 | __cvmx_cmd_queue_state_ptrs[node] = |
| 96 | (__cvmx_cmd_queue_all_state_t *)cvmx_phys_to_ptr( |
| 97 | block_desc->base_addr); |
| 98 | return CVMX_CMD_QUEUE_SUCCESS; |
| 99 | } |
| 100 | |
| 101 | size = sizeof(*__cvmx_cmd_queue_state_ptrs[node]); |
| 102 | |
| 103 | /* Rest f the code is to allocate a new named block */ |
| 104 | |
| 105 | /* Atomically allocate named block once, and zero it by default */ |
| 106 | ptr = cvmx_bootmem_alloc_named_range_once(size, paddr_min, paddr_max, |
| 107 | 128, alloc_name, NULL); |
| 108 | |
| 109 | if (ptr) { |
| 110 | __cvmx_cmd_queue_state_ptrs[node] = |
| 111 | (__cvmx_cmd_queue_all_state_t *)ptr; |
| 112 | } else { |
| 113 | debug("ERROR: %s: Unable to get named block %s.\n", __func__, |
| 114 | alloc_name); |
| 115 | return CVMX_CMD_QUEUE_NO_MEMORY; |
| 116 | } |
| 117 | return CVMX_CMD_QUEUE_SUCCESS; |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Initialize a command queue for use. The initial FPA buffer is |
| 122 | * allocated and the hardware unit is configured to point to the |
| 123 | * new command queue. |
| 124 | * |
| 125 | * @param queue_id Hardware command queue to initialize. |
| 126 | * @param max_depth Maximum outstanding commands that can be queued. |
| 127 | * @param fpa_pool FPA pool the command queues should come from. |
| 128 | * @param pool_size Size of each buffer in the FPA pool (bytes) |
| 129 | * |
| 130 | * @return CVMX_CMD_QUEUE_SUCCESS or a failure code |
| 131 | */ |
| 132 | cvmx_cmd_queue_result_t cvmx_cmd_queue_initialize(cvmx_cmd_queue_id_t queue_id, |
| 133 | int max_depth, int fpa_pool, |
| 134 | int pool_size) |
| 135 | { |
| 136 | __cvmx_cmd_queue_state_t *qstate; |
| 137 | cvmx_cmd_queue_result_t result; |
| 138 | unsigned int node; |
| 139 | unsigned int index; |
| 140 | int fpa_pool_min, fpa_pool_max; |
| 141 | union cvmx_fpa_ctl_status status; |
| 142 | void *buffer; |
| 143 | |
| 144 | node = __cvmx_cmd_queue_get_node(queue_id); |
| 145 | |
| 146 | index = __cvmx_cmd_queue_get_index(queue_id); |
| 147 | if (index >= NUM_ELEMENTS(__cvmx_cmd_queue_state_ptrs[node]->state)) { |
| 148 | printf("ERROR: %s: queue %#x out of range\n", __func__, |
| 149 | queue_id); |
| 150 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 151 | } |
| 152 | |
| 153 | result = __cvmx_cmd_queue_init_state_ptr(node); |
| 154 | if (result != CVMX_CMD_QUEUE_SUCCESS) |
| 155 | return result; |
| 156 | |
| 157 | qstate = __cvmx_cmd_queue_get_state(queue_id); |
| 158 | if (!qstate) |
| 159 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 160 | |
| 161 | /* |
| 162 | * We artificially limit max_depth to 1<<20 words. It is an |
| 163 | * arbitrary limit. |
| 164 | */ |
| 165 | if (CVMX_CMD_QUEUE_ENABLE_MAX_DEPTH) { |
| 166 | if (max_depth < 0 || max_depth > 1 << 20) |
| 167 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 168 | } else if (max_depth != 0) { |
| 169 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 170 | } |
| 171 | |
| 172 | /* CVMX_FPA_NUM_POOLS maps to cvmx_fpa3_num_auras for FPA3 */ |
| 173 | fpa_pool_min = node << 10; |
| 174 | fpa_pool_max = fpa_pool_min + CVMX_FPA_NUM_POOLS; |
| 175 | |
| 176 | if (fpa_pool < fpa_pool_min || fpa_pool >= fpa_pool_max) |
| 177 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 178 | |
| 179 | if (pool_size < 128 || pool_size > (1 << 17)) |
| 180 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 181 | |
| 182 | if (pool_size & 3) |
| 183 | debug("WARNING: %s: pool_size %d not multiple of 8\n", __func__, |
| 184 | pool_size); |
| 185 | |
| 186 | /* See if someone else has already initialized the queue */ |
| 187 | if (qstate->base_paddr) { |
| 188 | int depth; |
| 189 | static const char emsg[] = /* Common error message part */ |
| 190 | "Queue already initialized with different "; |
| 191 | |
| 192 | depth = (max_depth + qstate->pool_size_m1 - 1) / |
| 193 | qstate->pool_size_m1; |
| 194 | if (depth != qstate->max_depth) { |
| 195 | depth = qstate->max_depth * qstate->pool_size_m1; |
| 196 | debug("ERROR: %s: %s max_depth (%d).\n", __func__, emsg, |
| 197 | depth); |
| 198 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 199 | } |
| 200 | if (fpa_pool != qstate->fpa_pool) { |
| 201 | debug("ERROR: %s: %s FPA pool (%d).\n", __func__, emsg, |
| 202 | (int)qstate->fpa_pool); |
| 203 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 204 | } |
| 205 | if ((pool_size >> 3) - 1 != qstate->pool_size_m1) { |
| 206 | debug("ERROR: %s: %s FPA pool size (%u).\n", __func__, |
| 207 | emsg, (qstate->pool_size_m1 + 1) << 3); |
| 208 | return CVMX_CMD_QUEUE_INVALID_PARAM; |
| 209 | } |
| 210 | return CVMX_CMD_QUEUE_ALREADY_SETUP; |
| 211 | } |
| 212 | |
| 213 | if (!(octeon_has_feature(OCTEON_FEATURE_FPA3))) { |
| 214 | status.u64 = csr_rd(CVMX_FPA_CTL_STATUS); |
| 215 | if (!status.s.enb) { |
| 216 | debug("ERROR: %s: FPA is not enabled.\n", |
| 217 | __func__); |
| 218 | return CVMX_CMD_QUEUE_NO_MEMORY; |
| 219 | } |
| 220 | } |
| 221 | buffer = cvmx_fpa_alloc(fpa_pool); |
| 222 | if (!buffer) { |
| 223 | debug("ERROR: %s: allocating first buffer.\n", __func__); |
| 224 | return CVMX_CMD_QUEUE_NO_MEMORY; |
| 225 | } |
| 226 | |
| 227 | index = (pool_size >> 3) - 1; |
| 228 | qstate->pool_size_m1 = index; |
| 229 | qstate->max_depth = (max_depth + index - 1) / index; |
| 230 | qstate->index = 0; |
| 231 | qstate->fpa_pool = fpa_pool; |
| 232 | qstate->base_paddr = cvmx_ptr_to_phys(buffer); |
| 233 | |
| 234 | /* Initialize lock */ |
| 235 | __cvmx_cmd_queue_lock_init(queue_id); |
| 236 | return CVMX_CMD_QUEUE_SUCCESS; |
| 237 | } |
| 238 | |
| 239 | /** |
| 240 | * Return the command buffer to be written to. The purpose of this |
| 241 | * function is to allow CVMX routine access to the low level buffer |
| 242 | * for initial hardware setup. User applications should not call this |
| 243 | * function directly. |
| 244 | * |
| 245 | * @param queue_id Command queue to query |
| 246 | * |
| 247 | * @return Command buffer or NULL on failure |
| 248 | */ |
| 249 | void *cvmx_cmd_queue_buffer(cvmx_cmd_queue_id_t queue_id) |
| 250 | { |
| 251 | __cvmx_cmd_queue_state_t *qptr = __cvmx_cmd_queue_get_state(queue_id); |
| 252 | |
| 253 | if (qptr && qptr->base_paddr) |
| 254 | return cvmx_phys_to_ptr((u64)qptr->base_paddr); |
| 255 | else |
| 256 | return NULL; |
| 257 | } |
| 258 | |
| 259 | static u64 *__cvmx_cmd_queue_add_blk(__cvmx_cmd_queue_state_t *qptr) |
| 260 | { |
| 261 | u64 *cmd_ptr; |
| 262 | u64 *new_buffer; |
| 263 | u64 new_paddr; |
| 264 | |
| 265 | /* Get base vaddr of current (full) block */ |
| 266 | cmd_ptr = (u64 *)cvmx_phys_to_ptr((u64)qptr->base_paddr); |
| 267 | |
| 268 | /* Allocate a new block from the per-queue pool */ |
| 269 | new_buffer = (u64 *)cvmx_fpa_alloc(qptr->fpa_pool); |
| 270 | |
| 271 | /* Check for allocation failure */ |
| 272 | if (cvmx_unlikely(!new_buffer)) |
| 273 | return NULL; |
| 274 | |
| 275 | /* Zero out the new block link pointer, |
| 276 | * in case this block will be filled to the rim |
| 277 | */ |
| 278 | new_buffer[qptr->pool_size_m1] = ~0ull; |
| 279 | |
| 280 | /* Get physical address of the new buffer */ |
| 281 | new_paddr = cvmx_ptr_to_phys(new_buffer); |
| 282 | |
| 283 | /* Store the physical link address at the end of current full block */ |
| 284 | cmd_ptr[qptr->pool_size_m1] = new_paddr; |
| 285 | |
| 286 | /* Store the physical address in the queue state structure */ |
| 287 | qptr->base_paddr = new_paddr; |
| 288 | qptr->index = 0; |
| 289 | |
| 290 | /* Return the virtual base of the new block */ |
| 291 | return new_buffer; |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * @INTERNAL |
| 296 | * Add command words into a queue, handles all the corener cases |
| 297 | * where only some of the words might fit into the current block, |
| 298 | * and a new block may need to be allocated. |
| 299 | * Locking and argument checks are done in the front-end in-line |
| 300 | * functions that call this one for the rare corner cases. |
| 301 | */ |
| 302 | cvmx_cmd_queue_result_t |
| 303 | __cvmx_cmd_queue_write_raw(cvmx_cmd_queue_id_t queue_id, |
| 304 | __cvmx_cmd_queue_state_t *qptr, int cmd_count, |
| 305 | const u64 *cmds) |
| 306 | { |
| 307 | u64 *cmd_ptr; |
| 308 | unsigned int index; |
| 309 | |
| 310 | cmd_ptr = (u64 *)cvmx_phys_to_ptr((u64)qptr->base_paddr); |
| 311 | index = qptr->index; |
| 312 | |
| 313 | /* Enforce queue depth limit, if enabled, once per block */ |
| 314 | if (CVMX_CMD_QUEUE_ENABLE_MAX_DEPTH && cvmx_unlikely(qptr->max_depth)) { |
| 315 | unsigned int depth = cvmx_cmd_queue_length(queue_id); |
| 316 | |
| 317 | depth /= qptr->pool_size_m1; |
| 318 | |
| 319 | if (cvmx_unlikely(depth > qptr->max_depth)) |
| 320 | return CVMX_CMD_QUEUE_FULL; |
| 321 | } |
| 322 | |
| 323 | /* |
| 324 | * If the block allocation fails, even the words that we wrote |
| 325 | * to the current block will not count because the 'index' will |
| 326 | * not be comitted. |
| 327 | * The loop is run 'count + 1' times to take care of the tail |
| 328 | * case, where the buffer is full to the rim, so the link |
| 329 | * pointer must be filled with a valid address. |
| 330 | */ |
| 331 | while (cmd_count >= 0) { |
| 332 | if (index >= qptr->pool_size_m1) { |
| 333 | /* Block is full, get another one and proceed */ |
| 334 | cmd_ptr = __cvmx_cmd_queue_add_blk(qptr); |
| 335 | |
| 336 | /* Baul on allocation error w/o comitting anything */ |
| 337 | if (cvmx_unlikely(!cmd_ptr)) |
| 338 | return CVMX_CMD_QUEUE_NO_MEMORY; |
| 339 | |
| 340 | /* Reset index for start of new block */ |
| 341 | index = 0; |
| 342 | } |
| 343 | /* Exit Loop on 'count + 1' iterations */ |
| 344 | if (cmd_count <= 0) |
| 345 | break; |
| 346 | /* Store commands into queue block while there is space */ |
| 347 | cmd_ptr[index++] = *cmds++; |
| 348 | cmd_count--; |
| 349 | } /* while cmd_count */ |
| 350 | |
| 351 | /* Commit added words if all is well */ |
| 352 | qptr->index = index; |
| 353 | |
| 354 | return CVMX_CMD_QUEUE_SUCCESS; |
| 355 | } |