1325618Ssbruno/* 2325618Ssbruno * BSD LICENSE 3325618Ssbruno * 4325618Ssbruno * Copyright(c) 2017 Cavium, Inc.. All rights reserved. 5325618Ssbruno * All rights reserved. 6325618Ssbruno * 7325618Ssbruno * Redistribution and use in source and binary forms, with or without 8325618Ssbruno * modification, are permitted provided that the following conditions 9325618Ssbruno * are met: 10325618Ssbruno * 11325618Ssbruno * * Redistributions of source code must retain the above copyright 12325618Ssbruno * notice, this list of conditions and the following disclaimer. 13325618Ssbruno * * Redistributions in binary form must reproduce the above copyright 14325618Ssbruno * notice, this list of conditions and the following disclaimer in 15325618Ssbruno * the documentation and/or other materials provided with the 16325618Ssbruno * distribution. 17325618Ssbruno * * Neither the name of Cavium, Inc. nor the names of its 18325618Ssbruno * contributors may be used to endorse or promote products derived 19325618Ssbruno * from this software without specific prior written permission. 20325618Ssbruno * 21325618Ssbruno * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22325618Ssbruno * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23325618Ssbruno * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24325618Ssbruno * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25325618Ssbruno * OWNER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26325618Ssbruno * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27325618Ssbruno * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28325618Ssbruno * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29325618Ssbruno * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30325618Ssbruno * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31325618Ssbruno * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32325618Ssbruno */ 33325618Ssbruno/*$FreeBSD: stable/11/sys/dev/liquidio/base/lio_console.c 325618 2017-11-09 19:52:56Z sbruno $*/ 34325618Ssbruno 35325618Ssbruno/* 36325618Ssbruno * @file lio_console.c 37325618Ssbruno */ 38325618Ssbruno 39325618Ssbruno#include "lio_bsd.h" 40325618Ssbruno#include "lio_common.h" 41325618Ssbruno#include "lio_droq.h" 42325618Ssbruno#include "lio_iq.h" 43325618Ssbruno#include "lio_response_manager.h" 44325618Ssbruno#include "lio_device.h" 45325618Ssbruno#include "lio_image.h" 46325618Ssbruno#include "lio_mem_ops.h" 47325618Ssbruno#include "lio_main.h" 48325618Ssbruno 49325618Ssbrunostatic void lio_get_uboot_version(struct octeon_device *oct); 50325618Ssbrunostatic void lio_remote_lock(void); 51325618Ssbrunostatic void lio_remote_unlock(void); 52325618Ssbrunostatic uint64_t cvmx_bootmem_phy_named_block_find(struct octeon_device *oct, 53325618Ssbruno const char *name, 54325618Ssbruno uint32_t flags); 55325618Ssbrunostatic int lio_console_read(struct octeon_device *oct, 56325618Ssbruno uint32_t console_num, char *buffer, 57325618Ssbruno uint32_t buf_size); 58325618Ssbruno 59325618Ssbruno#define CAST_ULL(v) ((unsigned long long)(v)) 60325618Ssbruno 61325618Ssbruno#define LIO_BOOTLOADER_PCI_READ_BUFFER_DATA_ADDR 0x0006c008 62325618Ssbruno#define LIO_BOOTLOADER_PCI_READ_BUFFER_LEN_ADDR 0x0006c004 63325618Ssbruno#define LIO_BOOTLOADER_PCI_READ_BUFFER_OWNER_ADDR 0x0006c000 64325618Ssbruno#define LIO_BOOTLOADER_PCI_READ_DESC_ADDR 0x0006c100 65325618Ssbruno#define LIO_BOOTLOADER_PCI_WRITE_BUFFER_STR_LEN 248 66325618Ssbruno 67325618Ssbruno#define LIO_PCI_IO_BUF_OWNER_OCTEON 0x00000001 68325618Ssbruno#define LIO_PCI_IO_BUF_OWNER_HOST 0x00000002 69325618Ssbruno 70325618Ssbruno#define LIO_PCI_CONSOLE_BLOCK_NAME "__pci_console" 71325618Ssbruno#define LIO_CONSOLE_POLL_INTERVAL_MS 100 /* 10 times per second */ 72325618Ssbruno 73325618Ssbruno/* 74325618Ssbruno * First three members of cvmx_bootmem_desc are left in original positions 75325618Ssbruno * for backwards compatibility. Assumes big endian target 76325618Ssbruno */ 77325618Ssbrunostruct cvmx_bootmem_desc { 78325618Ssbruno /* lock to control access to list */ 79325618Ssbruno uint32_t lock; 80325618Ssbruno 81325618Ssbruno /* flags for indicating various conditions */ 82325618Ssbruno uint32_t flags; 83325618Ssbruno 84325618Ssbruno uint64_t head_addr; 85325618Ssbruno 86325618Ssbruno /* incremented changed when incompatible changes made */ 87325618Ssbruno uint32_t major_version; 88325618Ssbruno 89325618Ssbruno /* 90325618Ssbruno * incremented changed when compatible changes made, reset to zero 91325618Ssbruno * when major incremented 92325618Ssbruno */ 93325618Ssbruno uint32_t minor_version; 94325618Ssbruno 95325618Ssbruno uint64_t app_data_addr; 96325618Ssbruno uint64_t app_data_size; 97325618Ssbruno 98325618Ssbruno /* number of elements in named blocks array */ 99325618Ssbruno uint32_t nb_num_blocks; 100325618Ssbruno 101325618Ssbruno /* length of name array in bootmem blocks */ 102325618Ssbruno uint32_t named_block_name_len; 103325618Ssbruno 104325618Ssbruno /* address of named memory block descriptors */ 105325618Ssbruno uint64_t named_block_array_addr; 106325618Ssbruno}; 107325618Ssbruno 108325618Ssbruno/* 109325618Ssbruno * Structure that defines a single console. 110325618Ssbruno * 111325618Ssbruno * Note: when read_index == write_index, the buffer is empty. The actual usable 112325618Ssbruno * size of each console is console_buf_size -1; 113325618Ssbruno */ 114325618Ssbrunostruct lio_pci_console { 115325618Ssbruno uint64_t input_base_addr; 116325618Ssbruno uint32_t input_read_index; 117325618Ssbruno uint32_t input_write_index; 118325618Ssbruno uint64_t output_base_addr; 119325618Ssbruno uint32_t output_read_index; 120325618Ssbruno uint32_t output_write_index; 121325618Ssbruno uint32_t lock; 122325618Ssbruno uint32_t buf_size; 123325618Ssbruno}; 124325618Ssbruno 125325618Ssbruno/* 126325618Ssbruno * This is the main container structure that contains all the information 127325618Ssbruno * about all PCI consoles. The address of this structure is passed to 128325618Ssbruno * various routines that operation on PCI consoles. 129325618Ssbruno */ 130325618Ssbrunostruct lio_pci_console_desc { 131325618Ssbruno uint32_t major_version; 132325618Ssbruno uint32_t minor_version; 133325618Ssbruno uint32_t lock; 134325618Ssbruno uint32_t flags; 135325618Ssbruno uint32_t num_consoles; 136325618Ssbruno uint32_t pad; 137325618Ssbruno /* must be 64 bit aligned here... */ 138325618Ssbruno /* Array of addresses of octeon_pci_console structures */ 139325618Ssbruno uint64_t console_addr_array[1]; 140325618Ssbruno /* Implicit storage for console_addr_array */ 141325618Ssbruno}; 142325618Ssbruno 143325618Ssbruno/* 144325618Ssbruno * This macro returns the size of a member of a structure. Logically it is 145325618Ssbruno * the same as "sizeof(s::field)" in C++, but C lacks the "::" operator. 146325618Ssbruno */ 147325618Ssbruno#define SIZEOF_FIELD(s, field) sizeof(((s *)NULL)->field) 148325618Ssbruno/* 149325618Ssbruno * This function is the implementation of the get macros defined 150325618Ssbruno * for individual structure members. The argument are generated 151325618Ssbruno * by the macros inorder to read only the needed memory. 152325618Ssbruno * 153325618Ssbruno * @param oct Pointer to current octeon device 154325618Ssbruno * @param base 64bit physical address of the complete structure 155325618Ssbruno * @param offset Offset from the beginning of the structure to the member being 156325618Ssbruno * accessed. 157325618Ssbruno * @param size Size of the structure member. 158325618Ssbruno * 159325618Ssbruno * @return Value of the structure member promoted into a uint64_t. 160325618Ssbruno */ 161325618Ssbrunostatic inline uint64_t 162325618Ssbruno__cvmx_bootmem_desc_get(struct octeon_device *oct, uint64_t base, 163325618Ssbruno uint32_t offset, uint32_t size) 164325618Ssbruno{ 165325618Ssbruno 166325618Ssbruno base = (1ull << 63) | (base + offset); 167325618Ssbruno switch (size) { 168325618Ssbruno case 4: 169325618Ssbruno return (lio_read_device_mem32(oct, base)); 170325618Ssbruno case 8: 171325618Ssbruno return (lio_read_device_mem64(oct, base)); 172325618Ssbruno default: 173325618Ssbruno return (0); 174325618Ssbruno } 175325618Ssbruno} 176325618Ssbruno 177325618Ssbruno/* 178325618Ssbruno * This function retrieves the string name of a named block. It is 179325618Ssbruno * more complicated than a simple memcpy() since the named block 180325618Ssbruno * descriptor may not be directly accessible. 181325618Ssbruno * 182325618Ssbruno * @param oct Pointer to current octeon device 183325618Ssbruno * @param addr Physical address of the named block descriptor 184325618Ssbruno * @param str String to receive the named block string name 185325618Ssbruno * @param len Length of the string buffer, which must match the length 186325618Ssbruno * stored in the bootmem descriptor. 187325618Ssbruno */ 188325618Ssbrunostatic void 189325618Ssbrunolio_bootmem_named_get_name(struct octeon_device *oct, uint64_t addr, char *str, 190325618Ssbruno uint32_t len) 191325618Ssbruno{ 192325618Ssbruno 193325618Ssbruno addr += offsetof(struct cvmx_bootmem_named_block_desc, name); 194325618Ssbruno lio_pci_read_core_mem(oct, addr, (uint8_t *) str, len); 195325618Ssbruno str[len] = 0; 196325618Ssbruno} 197325618Ssbruno 198325618Ssbruno/* See header file for descriptions of functions */ 199325618Ssbruno 200325618Ssbruno/* 201325618Ssbruno * Check the version information on the bootmem descriptor 202325618Ssbruno * 203325618Ssbruno * @param oct Pointer to current octeon device 204325618Ssbruno * @param exact_match 205325618Ssbruno * Exact major version to check against. A zero means 206325618Ssbruno * check that the version supports named blocks. 207325618Ssbruno * 208325618Ssbruno * @return Zero if the version is correct. Negative if the version is 209325618Ssbruno * incorrect. Failures also cause a message to be displayed. 210325618Ssbruno */ 211325618Ssbrunostatic int 212325618Ssbruno__cvmx_bootmem_check_version(struct octeon_device *oct, uint32_t exact_match) 213325618Ssbruno{ 214325618Ssbruno uint32_t major_version; 215325618Ssbruno uint32_t minor_version; 216325618Ssbruno 217325618Ssbruno if (!oct->bootmem_desc_addr) 218325618Ssbruno oct->bootmem_desc_addr = 219325618Ssbruno lio_read_device_mem64(oct, 220325618Ssbruno LIO_BOOTLOADER_PCI_READ_DESC_ADDR); 221325618Ssbruno 222325618Ssbruno major_version = (uint32_t) __cvmx_bootmem_desc_get(oct, 223325618Ssbruno oct->bootmem_desc_addr, 224325618Ssbruno offsetof(struct cvmx_bootmem_desc, major_version), 225325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_desc, major_version)); 226325618Ssbruno minor_version = (uint32_t) __cvmx_bootmem_desc_get(oct, 227325618Ssbruno oct->bootmem_desc_addr, 228325618Ssbruno offsetof(struct cvmx_bootmem_desc, minor_version), 229325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_desc, minor_version)); 230325618Ssbruno 231325618Ssbruno lio_dev_dbg(oct, "%s: major_version=%d\n", __func__, major_version); 232325618Ssbruno if ((major_version > 3) || 233325618Ssbruno (exact_match && major_version != exact_match)) { 234325618Ssbruno lio_dev_err(oct, "bootmem ver mismatch %d.%d addr:0x%llx\n", 235325618Ssbruno major_version, minor_version, 236325618Ssbruno CAST_ULL(oct->bootmem_desc_addr)); 237325618Ssbruno return (-1); 238325618Ssbruno } else { 239325618Ssbruno return (0); 240325618Ssbruno } 241325618Ssbruno} 242325618Ssbruno 243325618Ssbrunostatic const struct cvmx_bootmem_named_block_desc * 244325618Ssbruno__cvmx_bootmem_find_named_block_flags(struct octeon_device *oct, 245325618Ssbruno const char *name, uint32_t flags) 246325618Ssbruno{ 247325618Ssbruno struct cvmx_bootmem_named_block_desc *desc = 248325618Ssbruno &oct->bootmem_named_block_desc; 249325618Ssbruno uint64_t named_addr; 250325618Ssbruno 251325618Ssbruno named_addr = cvmx_bootmem_phy_named_block_find(oct, name, 252325618Ssbruno flags); 253325618Ssbruno if (named_addr) { 254325618Ssbruno desc->base_addr = __cvmx_bootmem_desc_get(oct, named_addr, 255325618Ssbruno offsetof(struct cvmx_bootmem_named_block_desc, 256325618Ssbruno base_addr), 257325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_named_block_desc, 258325618Ssbruno base_addr)); 259325618Ssbruno 260325618Ssbruno desc->size = __cvmx_bootmem_desc_get(oct, named_addr, 261325618Ssbruno offsetof(struct cvmx_bootmem_named_block_desc, size), 262325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_named_block_desc, 263325618Ssbruno size)); 264325618Ssbruno 265325618Ssbruno strncpy(desc->name, name, sizeof(desc->name)); 266325618Ssbruno desc->name[sizeof(desc->name) - 1] = 0; 267325618Ssbruno 268325618Ssbruno return (&oct->bootmem_named_block_desc); 269325618Ssbruno } else { 270325618Ssbruno return (NULL); 271325618Ssbruno } 272325618Ssbruno} 273325618Ssbruno 274325618Ssbrunostatic uint64_t 275325618Ssbrunocvmx_bootmem_phy_named_block_find(struct octeon_device *oct, const char *name, 276325618Ssbruno uint32_t flags) 277325618Ssbruno{ 278325618Ssbruno uint64_t result = 0; 279325618Ssbruno 280325618Ssbruno if (!__cvmx_bootmem_check_version(oct, 3)) { 281325618Ssbruno uint32_t i; 282325618Ssbruno 283325618Ssbruno uint64_t named_block_array_addr = 284325618Ssbruno __cvmx_bootmem_desc_get(oct, oct->bootmem_desc_addr, 285325618Ssbruno offsetof(struct cvmx_bootmem_desc, 286325618Ssbruno named_block_array_addr), 287325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_desc, 288325618Ssbruno named_block_array_addr)); 289325618Ssbruno uint32_t num_blocks = 290325618Ssbruno (uint32_t) __cvmx_bootmem_desc_get(oct, 291325618Ssbruno oct->bootmem_desc_addr, 292325618Ssbruno offsetof(struct cvmx_bootmem_desc, 293325618Ssbruno nb_num_blocks), 294325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_desc, 295325618Ssbruno nb_num_blocks)); 296325618Ssbruno 297325618Ssbruno uint32_t name_length = 298325618Ssbruno (uint32_t) __cvmx_bootmem_desc_get(oct, 299325618Ssbruno oct->bootmem_desc_addr, 300325618Ssbruno offsetof(struct cvmx_bootmem_desc, 301325618Ssbruno named_block_name_len), 302325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_desc, 303325618Ssbruno named_block_name_len)); 304325618Ssbruno 305325618Ssbruno uint64_t named_addr = named_block_array_addr; 306325618Ssbruno 307325618Ssbruno for (i = 0; i < num_blocks; i++) { 308325618Ssbruno uint64_t named_size = 309325618Ssbruno __cvmx_bootmem_desc_get(oct, named_addr, 310325618Ssbruno offsetof(struct cvmx_bootmem_named_block_desc, 311325618Ssbruno size), 312325618Ssbruno SIZEOF_FIELD(struct cvmx_bootmem_named_block_desc, 313325618Ssbruno size)); 314325618Ssbruno 315325618Ssbruno if (name && named_size) { 316325618Ssbruno char *name_tmp = malloc(name_length + 1, 317325618Ssbruno M_DEVBUF, M_NOWAIT | 318325618Ssbruno M_ZERO); 319325618Ssbruno if (!name_tmp) 320325618Ssbruno break; 321325618Ssbruno 322325618Ssbruno lio_bootmem_named_get_name(oct, named_addr, 323325618Ssbruno name_tmp, 324325618Ssbruno name_length); 325325618Ssbruno 326325618Ssbruno if (!strncmp(name, name_tmp, name_length)) { 327325618Ssbruno result = named_addr; 328325618Ssbruno free(name_tmp, M_DEVBUF); 329325618Ssbruno break; 330325618Ssbruno } 331325618Ssbruno 332325618Ssbruno free(name_tmp, M_DEVBUF); 333325618Ssbruno 334325618Ssbruno } else if (!name && !named_size) { 335325618Ssbruno result = named_addr; 336325618Ssbruno break; 337325618Ssbruno } 338325618Ssbruno 339325618Ssbruno named_addr += 340325618Ssbruno sizeof(struct cvmx_bootmem_named_block_desc); 341325618Ssbruno } 342325618Ssbruno } 343325618Ssbruno return (result); 344325618Ssbruno} 345325618Ssbruno 346325618Ssbruno/* 347325618Ssbruno * Find a named block on the remote Octeon 348325618Ssbruno * 349325618Ssbruno * @param oct Pointer to current octeon device 350325618Ssbruno * @param name Name of block to find 351325618Ssbruno * @param base_addr Address the block is at (OUTPUT) 352325618Ssbruno * @param size The size of the block (OUTPUT) 353325618Ssbruno * 354325618Ssbruno * @return Zero on success, One on failure. 355325618Ssbruno */ 356325618Ssbrunostatic int 357325618Ssbrunolio_named_block_find(struct octeon_device *oct, const char *name, 358325618Ssbruno uint64_t * base_addr, uint64_t * size) 359325618Ssbruno{ 360325618Ssbruno const struct cvmx_bootmem_named_block_desc *named_block; 361325618Ssbruno 362325618Ssbruno lio_remote_lock(); 363325618Ssbruno named_block = __cvmx_bootmem_find_named_block_flags(oct, name, 0); 364325618Ssbruno lio_remote_unlock(); 365325618Ssbruno if (named_block != NULL) { 366325618Ssbruno *base_addr = named_block->base_addr; 367325618Ssbruno *size = named_block->size; 368325618Ssbruno return (0); 369325618Ssbruno } 370325618Ssbruno 371325618Ssbruno return (1); 372325618Ssbruno} 373325618Ssbruno 374325618Ssbruno 375325618Ssbrunostatic void 376325618Ssbrunolio_remote_lock(void) 377325618Ssbruno{ 378325618Ssbruno 379325618Ssbruno /* fill this in if any sharing is needed */ 380325618Ssbruno} 381325618Ssbruno 382325618Ssbrunostatic void 383325618Ssbrunolio_remote_unlock(void) 384325618Ssbruno{ 385325618Ssbruno 386325618Ssbruno /* fill this in if any sharing is needed */ 387325618Ssbruno} 388325618Ssbruno 389325618Ssbrunoint 390325618Ssbrunolio_console_send_cmd(struct octeon_device *oct, char *cmd_str, 391325618Ssbruno uint32_t wait_hundredths) 392325618Ssbruno{ 393325618Ssbruno uint32_t len = (uint32_t) strlen(cmd_str); 394325618Ssbruno 395325618Ssbruno lio_dev_dbg(oct, "sending \"%s\" to bootloader\n", cmd_str); 396325618Ssbruno 397325618Ssbruno if (len > LIO_BOOTLOADER_PCI_WRITE_BUFFER_STR_LEN - 1) { 398325618Ssbruno lio_dev_err(oct, "Command string too long, max length is: %d\n", 399325618Ssbruno LIO_BOOTLOADER_PCI_WRITE_BUFFER_STR_LEN - 1); 400325618Ssbruno return (-1); 401325618Ssbruno } 402325618Ssbruno 403325618Ssbruno if (lio_wait_for_bootloader(oct, wait_hundredths)) { 404325618Ssbruno lio_dev_err(oct, "Bootloader not ready for command.\n"); 405325618Ssbruno return (-1); 406325618Ssbruno } 407325618Ssbruno 408325618Ssbruno /* Write command to bootloader */ 409325618Ssbruno lio_remote_lock(); 410325618Ssbruno lio_pci_write_core_mem(oct, LIO_BOOTLOADER_PCI_READ_BUFFER_DATA_ADDR, 411325618Ssbruno (uint8_t *) cmd_str, len); 412325618Ssbruno lio_write_device_mem32(oct, LIO_BOOTLOADER_PCI_READ_BUFFER_LEN_ADDR, 413325618Ssbruno len); 414325618Ssbruno lio_write_device_mem32(oct, LIO_BOOTLOADER_PCI_READ_BUFFER_OWNER_ADDR, 415325618Ssbruno LIO_PCI_IO_BUF_OWNER_OCTEON); 416325618Ssbruno 417325618Ssbruno /* 418325618Ssbruno * Bootloader should accept command very quickly if it really was 419325618Ssbruno * ready 420325618Ssbruno */ 421325618Ssbruno if (lio_wait_for_bootloader(oct, 200)) { 422325618Ssbruno lio_remote_unlock(); 423325618Ssbruno lio_dev_err(oct, "Bootloader did not accept command.\n"); 424325618Ssbruno return (-1); 425325618Ssbruno } 426325618Ssbruno 427325618Ssbruno lio_remote_unlock(); 428325618Ssbruno return (0); 429325618Ssbruno} 430325618Ssbruno 431325618Ssbrunoint 432325618Ssbrunolio_wait_for_bootloader(struct octeon_device *oct, 433325618Ssbruno uint32_t wait_time_hundredths) 434325618Ssbruno{ 435325618Ssbruno lio_dev_dbg(oct, "waiting %d0 ms for bootloader\n", 436325618Ssbruno wait_time_hundredths); 437325618Ssbruno 438325618Ssbruno if (lio_mem_access_ok(oct)) 439325618Ssbruno return (-1); 440325618Ssbruno 441325618Ssbruno while (wait_time_hundredths > 0 && 442325618Ssbruno lio_read_device_mem32(oct, 443325618Ssbruno LIO_BOOTLOADER_PCI_READ_BUFFER_OWNER_ADDR) != 444325618Ssbruno LIO_PCI_IO_BUF_OWNER_HOST) { 445325618Ssbruno if (--wait_time_hundredths <= 0) 446325618Ssbruno return (-1); 447325618Ssbruno 448325618Ssbruno lio_sleep_timeout(10); 449325618Ssbruno } 450325618Ssbruno 451325618Ssbruno return (0); 452325618Ssbruno} 453325618Ssbruno 454325618Ssbrunostatic void 455325618Ssbrunolio_console_handle_result(struct octeon_device *oct, size_t console_num) 456325618Ssbruno{ 457325618Ssbruno struct lio_console *console; 458325618Ssbruno 459325618Ssbruno console = &oct->console[console_num]; 460325618Ssbruno 461325618Ssbruno console->waiting = 0; 462325618Ssbruno} 463325618Ssbruno 464325618Ssbrunostatic char console_buffer[LIO_MAX_CONSOLE_READ_BYTES]; 465325618Ssbruno 466325618Ssbrunostatic void 467325618Ssbrunolio_output_console_line(struct octeon_device *oct, struct lio_console *console, 468325618Ssbruno size_t console_num, char *console_buffer, 469325618Ssbruno int32_t bytes_read) 470325618Ssbruno{ 471325618Ssbruno size_t len; 472325618Ssbruno int32_t i; 473325618Ssbruno char *line; 474325618Ssbruno 475325618Ssbruno line = console_buffer; 476325618Ssbruno for (i = 0; i < bytes_read; i++) { 477325618Ssbruno /* Output a line at a time, prefixed */ 478325618Ssbruno if (console_buffer[i] == '\n') { 479325618Ssbruno console_buffer[i] = '\0'; 480325618Ssbruno /* We need to output 'line', prefaced by 'leftover'. 481325618Ssbruno * However, it is possible we're being called to 482325618Ssbruno * output 'leftover' by itself (in the case of nothing 483325618Ssbruno * having been read from the console). 484325618Ssbruno * 485325618Ssbruno * To avoid duplication, check for this condition. 486325618Ssbruno */ 487325618Ssbruno if (console->leftover[0] && 488325618Ssbruno (line != console->leftover)) { 489325618Ssbruno if (console->print) 490325618Ssbruno (*console->print)(oct, 491325618Ssbruno (uint32_t)console_num, 492325618Ssbruno console->leftover,line); 493325618Ssbruno console->leftover[0] = '\0'; 494325618Ssbruno } else { 495325618Ssbruno if (console->print) 496325618Ssbruno (*console->print)(oct, 497325618Ssbruno (uint32_t)console_num, 498325618Ssbruno line, NULL); 499325618Ssbruno } 500325618Ssbruno 501325618Ssbruno line = &console_buffer[i + 1]; 502325618Ssbruno } 503325618Ssbruno } 504325618Ssbruno 505325618Ssbruno /* Save off any leftovers */ 506325618Ssbruno if (line != &console_buffer[bytes_read]) { 507325618Ssbruno console_buffer[bytes_read] = '\0'; 508325618Ssbruno len = strlen(console->leftover); 509325618Ssbruno strncpy(&console->leftover[len], line, 510325618Ssbruno sizeof(console->leftover) - len); 511325618Ssbruno } 512325618Ssbruno} 513325618Ssbruno 514325618Ssbrunostatic void 515325618Ssbrunolio_check_console(void *arg) 516325618Ssbruno{ 517325618Ssbruno struct lio_console *console; 518325618Ssbruno struct lio_callout *console_callout = arg; 519325618Ssbruno struct octeon_device *oct = 520325618Ssbruno (struct octeon_device *)console_callout->ctxptr; 521325618Ssbruno size_t len; 522325618Ssbruno uint32_t console_num = (uint32_t) console_callout->ctxul; 523325618Ssbruno int32_t bytes_read, total_read, tries; 524325618Ssbruno 525325618Ssbruno console = &oct->console[console_num]; 526325618Ssbruno tries = 0; 527325618Ssbruno total_read = 0; 528325618Ssbruno 529325618Ssbruno if (callout_pending(&console_callout->timer) || 530325618Ssbruno (callout_active(&console_callout->timer) == 0)) 531325618Ssbruno return; 532325618Ssbruno 533325618Ssbruno do { 534325618Ssbruno /* 535325618Ssbruno * Take console output regardless of whether it will be 536325618Ssbruno * logged 537325618Ssbruno */ 538325618Ssbruno bytes_read = lio_console_read(oct, console_num, console_buffer, 539325618Ssbruno sizeof(console_buffer) - 1); 540325618Ssbruno if (bytes_read > 0) { 541325618Ssbruno total_read += bytes_read; 542325618Ssbruno if (console->waiting) 543325618Ssbruno lio_console_handle_result(oct, console_num); 544325618Ssbruno 545325618Ssbruno if (console->print) { 546325618Ssbruno lio_output_console_line(oct, console, 547325618Ssbruno console_num, 548325618Ssbruno console_buffer, 549325618Ssbruno bytes_read); 550325618Ssbruno } 551325618Ssbruno 552325618Ssbruno } else if (bytes_read < 0) { 553325618Ssbruno lio_dev_err(oct, "Error reading console %u, ret=%d\n", 554325618Ssbruno console_num, bytes_read); 555325618Ssbruno } 556325618Ssbruno 557325618Ssbruno tries++; 558325618Ssbruno } while ((bytes_read > 0) && (tries < 16)); 559325618Ssbruno 560325618Ssbruno /* 561325618Ssbruno * If nothing is read after polling the console, output any leftovers 562325618Ssbruno * if any 563325618Ssbruno */ 564325618Ssbruno if (console->print && (total_read == 0) && (console->leftover[0])) { 565325618Ssbruno /* append '\n' as terminator for 'output_console_line' */ 566325618Ssbruno len = strlen(console->leftover); 567325618Ssbruno console->leftover[len] = '\n'; 568325618Ssbruno lio_output_console_line(oct, console, console_num, 569325618Ssbruno console->leftover, (int32_t)(len + 1)); 570325618Ssbruno console->leftover[0] = '\0'; 571325618Ssbruno } 572325618Ssbruno callout_schedule(&oct->console_timer[console_num].timer, 573325618Ssbruno lio_ms_to_ticks(LIO_CONSOLE_POLL_INTERVAL_MS)); 574325618Ssbruno} 575325618Ssbruno 576325618Ssbruno 577325618Ssbrunoint 578325618Ssbrunolio_init_consoles(struct octeon_device *oct) 579325618Ssbruno{ 580325618Ssbruno uint64_t addr, size; 581325618Ssbruno int ret = 0; 582325618Ssbruno 583325618Ssbruno ret = lio_mem_access_ok(oct); 584325618Ssbruno if (ret) { 585325618Ssbruno lio_dev_err(oct, "Memory access not okay'\n"); 586325618Ssbruno return (ret); 587325618Ssbruno } 588325618Ssbruno ret = lio_named_block_find(oct, LIO_PCI_CONSOLE_BLOCK_NAME, &addr, 589325618Ssbruno &size); 590325618Ssbruno if (ret) { 591325618Ssbruno lio_dev_err(oct, "Could not find console '%s'\n", 592325618Ssbruno LIO_PCI_CONSOLE_BLOCK_NAME); 593325618Ssbruno return (ret); 594325618Ssbruno } 595325618Ssbruno 596325618Ssbruno /* 597325618Ssbruno * Use BAR1_INDEX15 to create a static mapping to a region of 598325618Ssbruno * Octeon's DRAM that contains the PCI console named block. 599325618Ssbruno */ 600325618Ssbruno oct->console_nb_info.bar1_index = 15; 601325618Ssbruno oct->fn_list.bar1_idx_setup(oct, addr, oct->console_nb_info.bar1_index, 602325618Ssbruno 1); 603325618Ssbruno oct->console_nb_info.dram_region_base = addr & 0xFFFFFFFFFFC00000ULL; 604325618Ssbruno 605325618Ssbruno /* 606325618Ssbruno * num_consoles > 0, is an indication that the consoles are 607325618Ssbruno * accessible 608325618Ssbruno */ 609325618Ssbruno oct->num_consoles = lio_read_device_mem32(oct, 610325618Ssbruno addr + offsetof(struct lio_pci_console_desc, 611325618Ssbruno num_consoles)); 612325618Ssbruno oct->console_desc_addr = addr; 613325618Ssbruno 614325618Ssbruno lio_dev_dbg(oct, "Initialized consoles. %d available\n", 615325618Ssbruno oct->num_consoles); 616325618Ssbruno 617325618Ssbruno return (ret); 618325618Ssbruno} 619325618Ssbruno 620325618Ssbrunoint 621325618Ssbrunolio_add_console(struct octeon_device *oct, uint32_t console_num, char *dbg_enb) 622325618Ssbruno{ 623325618Ssbruno struct callout *timer; 624325618Ssbruno struct lio_console *console; 625325618Ssbruno uint64_t coreaddr; 626325618Ssbruno int ret = 0; 627325618Ssbruno 628325618Ssbruno if (console_num >= oct->num_consoles) { 629325618Ssbruno lio_dev_err(oct, "trying to read from console number %d when only 0 to %d exist\n", 630325618Ssbruno console_num, oct->num_consoles); 631325618Ssbruno } else { 632325618Ssbruno console = &oct->console[console_num]; 633325618Ssbruno 634325618Ssbruno console->waiting = 0; 635325618Ssbruno 636325618Ssbruno coreaddr = oct->console_desc_addr + console_num * 8 + 637325618Ssbruno offsetof(struct lio_pci_console_desc, 638325618Ssbruno console_addr_array); 639325618Ssbruno console->addr = lio_read_device_mem64(oct, coreaddr); 640325618Ssbruno coreaddr = console->addr + offsetof(struct lio_pci_console, 641325618Ssbruno buf_size); 642325618Ssbruno console->buffer_size = lio_read_device_mem32(oct, coreaddr); 643325618Ssbruno coreaddr = console->addr + offsetof(struct lio_pci_console, 644325618Ssbruno input_base_addr); 645325618Ssbruno console->input_base_addr = lio_read_device_mem64(oct, coreaddr); 646325618Ssbruno coreaddr = console->addr + offsetof(struct lio_pci_console, 647325618Ssbruno output_base_addr); 648325618Ssbruno console->output_base_addr = 649325618Ssbruno lio_read_device_mem64(oct, coreaddr); 650325618Ssbruno console->leftover[0] = '\0'; 651325618Ssbruno 652325618Ssbruno timer = &oct->console_timer[console_num].timer; 653325618Ssbruno 654325618Ssbruno if (oct->uboot_len == 0) 655325618Ssbruno lio_get_uboot_version(oct); 656325618Ssbruno 657325618Ssbruno callout_init(timer, 0); 658325618Ssbruno oct->console_timer[console_num].ctxptr = (void *)oct; 659325618Ssbruno oct->console_timer[console_num].ctxul = console_num; 660325618Ssbruno callout_reset(timer, 661325618Ssbruno lio_ms_to_ticks(LIO_CONSOLE_POLL_INTERVAL_MS), 662325618Ssbruno lio_check_console, timer); 663325618Ssbruno /* an empty string means use default debug console enablement */ 664325618Ssbruno if (dbg_enb && !dbg_enb[0]) 665325618Ssbruno dbg_enb = "setenv pci_console_active 1"; 666325618Ssbruno 667325618Ssbruno if (dbg_enb) 668325618Ssbruno ret = lio_console_send_cmd(oct, dbg_enb, 2000); 669325618Ssbruno 670325618Ssbruno console->active = 1; 671325618Ssbruno } 672325618Ssbruno 673325618Ssbruno return (ret); 674325618Ssbruno} 675325618Ssbruno 676325618Ssbruno/* 677325618Ssbruno * Removes all consoles 678325618Ssbruno * 679325618Ssbruno * @param oct octeon device 680325618Ssbruno */ 681325618Ssbrunovoid 682325618Ssbrunolio_remove_consoles(struct octeon_device *oct) 683325618Ssbruno{ 684325618Ssbruno struct lio_console *console; 685325618Ssbruno uint32_t i; 686325618Ssbruno 687325618Ssbruno for (i = 0; i < oct->num_consoles; i++) { 688325618Ssbruno console = &oct->console[i]; 689325618Ssbruno 690325618Ssbruno if (!console->active) 691325618Ssbruno continue; 692325618Ssbruno 693325618Ssbruno callout_stop(&oct->console_timer[i].timer); 694325618Ssbruno console->addr = 0; 695325618Ssbruno console->buffer_size = 0; 696325618Ssbruno console->input_base_addr = 0; 697325618Ssbruno console->output_base_addr = 0; 698325618Ssbruno } 699325618Ssbruno 700325618Ssbruno oct->num_consoles = 0; 701325618Ssbruno} 702325618Ssbruno 703325618Ssbrunostatic inline int 704325618Ssbrunolio_console_free_bytes(uint32_t buffer_size, uint32_t wr_idx, uint32_t rd_idx) 705325618Ssbruno{ 706325618Ssbruno 707325618Ssbruno if (rd_idx >= buffer_size || wr_idx >= buffer_size) 708325618Ssbruno return (-1); 709325618Ssbruno 710325618Ssbruno return (((buffer_size - 1) - (wr_idx - rd_idx)) % buffer_size); 711325618Ssbruno} 712325618Ssbruno 713325618Ssbrunostatic inline int 714325618Ssbrunolio_console_avail_bytes(uint32_t buffer_size, uint32_t wr_idx, uint32_t rd_idx) 715325618Ssbruno{ 716325618Ssbruno 717325618Ssbruno if (rd_idx >= buffer_size || wr_idx >= buffer_size) 718325618Ssbruno return (-1); 719325618Ssbruno 720325618Ssbruno return (buffer_size - 1 - 721325618Ssbruno lio_console_free_bytes(buffer_size, wr_idx, rd_idx)); 722325618Ssbruno} 723325618Ssbruno 724325618Ssbrunostatic int 725325618Ssbrunolio_console_read(struct octeon_device *oct, uint32_t console_num, char *buffer, 726325618Ssbruno uint32_t buf_size) 727325618Ssbruno{ 728325618Ssbruno struct lio_console *console; 729325618Ssbruno int bytes_to_read; 730325618Ssbruno uint32_t rd_idx, wr_idx; 731325618Ssbruno 732325618Ssbruno if (console_num >= oct->num_consoles) { 733325618Ssbruno lio_dev_err(oct, "Attempted to read from disabled console %d\n", 734325618Ssbruno console_num); 735325618Ssbruno return (0); 736325618Ssbruno } 737325618Ssbruno 738325618Ssbruno console = &oct->console[console_num]; 739325618Ssbruno 740325618Ssbruno /* 741325618Ssbruno * Check to see if any data is available. Maybe optimize this with 742325618Ssbruno * 64-bit read. 743325618Ssbruno */ 744325618Ssbruno rd_idx = lio_read_device_mem32(oct, console->addr + 745325618Ssbruno offsetof(struct lio_pci_console, output_read_index)); 746325618Ssbruno wr_idx = lio_read_device_mem32(oct, console->addr + 747325618Ssbruno offsetof(struct lio_pci_console, output_write_index)); 748325618Ssbruno 749325618Ssbruno bytes_to_read = lio_console_avail_bytes(console->buffer_size, 750325618Ssbruno wr_idx, rd_idx); 751325618Ssbruno if (bytes_to_read <= 0) 752325618Ssbruno return (bytes_to_read); 753325618Ssbruno 754325618Ssbruno bytes_to_read = min(bytes_to_read, buf_size); 755325618Ssbruno 756325618Ssbruno /* 757325618Ssbruno * Check to see if what we want to read is not contiguous, and limit 758325618Ssbruno * ourselves to the contiguous block 759325618Ssbruno */ 760325618Ssbruno if (rd_idx + bytes_to_read >= console->buffer_size) 761325618Ssbruno bytes_to_read = console->buffer_size - rd_idx; 762325618Ssbruno 763325618Ssbruno lio_pci_read_core_mem(oct, console->output_base_addr + rd_idx, 764325618Ssbruno (uint8_t *) buffer, bytes_to_read); 765325618Ssbruno lio_write_device_mem32(oct, console->addr + 766325618Ssbruno offsetof(struct lio_pci_console, 767325618Ssbruno output_read_index), 768325618Ssbruno (rd_idx + bytes_to_read) % console->buffer_size); 769325618Ssbruno 770325618Ssbruno return (bytes_to_read); 771325618Ssbruno} 772325618Ssbruno 773325618Ssbrunostatic void 774325618Ssbrunolio_get_uboot_version(struct octeon_device *oct) 775325618Ssbruno{ 776325618Ssbruno struct lio_console *console; 777325618Ssbruno int32_t bytes_read, total_read, tries; 778325618Ssbruno uint32_t console_num = 0; 779325618Ssbruno int i, ret = 0; 780325618Ssbruno 781325618Ssbruno ret = lio_console_send_cmd(oct, "setenv stdout pci", 50); 782325618Ssbruno 783325618Ssbruno console = &oct->console[console_num]; 784325618Ssbruno tries = 0; 785325618Ssbruno total_read = 0; 786325618Ssbruno 787325618Ssbruno ret = lio_console_send_cmd(oct, "version", 1); 788325618Ssbruno 789325618Ssbruno do { 790325618Ssbruno /* 791325618Ssbruno * Take console output regardless of whether it will be 792325618Ssbruno * logged 793325618Ssbruno */ 794325618Ssbruno bytes_read = lio_console_read(oct, 795325618Ssbruno console_num, oct->uboot_version + 796325618Ssbruno total_read, 797325618Ssbruno OCTEON_UBOOT_BUFFER_SIZE - 1 - 798325618Ssbruno total_read); 799325618Ssbruno if (bytes_read > 0) { 800325618Ssbruno oct->uboot_version[bytes_read] = 0x0; 801325618Ssbruno 802325618Ssbruno total_read += bytes_read; 803325618Ssbruno if (console->waiting) 804325618Ssbruno lio_console_handle_result(oct, console_num); 805325618Ssbruno 806325618Ssbruno } else if (bytes_read < 0) { 807325618Ssbruno lio_dev_err(oct, "Error reading console %u, ret=%d\n", 808325618Ssbruno console_num, bytes_read); 809325618Ssbruno } 810325618Ssbruno 811325618Ssbruno tries++; 812325618Ssbruno } while ((bytes_read > 0) && (tries < 16)); 813325618Ssbruno 814325618Ssbruno /* 815325618Ssbruno * If nothing is read after polling the console, output any leftovers 816325618Ssbruno * if any 817325618Ssbruno */ 818325618Ssbruno if ((total_read == 0) && (console->leftover[0])) { 819325618Ssbruno lio_dev_dbg(oct, "%u: %s\n", console_num, console->leftover); 820325618Ssbruno console->leftover[0] = '\0'; 821325618Ssbruno } 822325618Ssbruno 823325618Ssbruno ret = lio_console_send_cmd(oct, "setenv stdout serial", 50); 824325618Ssbruno 825325618Ssbruno /* U-Boot */ 826325618Ssbruno for (i = 0; i < (OCTEON_UBOOT_BUFFER_SIZE - 9); i++) { 827325618Ssbruno if (oct->uboot_version[i] == 'U' && 828325618Ssbruno oct->uboot_version[i + 2] == 'B' && 829325618Ssbruno oct->uboot_version[i + 3] == 'o' && 830325618Ssbruno oct->uboot_version[i + 4] == 'o' && 831325618Ssbruno oct->uboot_version[i + 5] == 't') { 832325618Ssbruno oct->uboot_sidx = i; 833325618Ssbruno i++; 834325618Ssbruno for (; oct->uboot_version[i] != 0x0; i++) { 835325618Ssbruno if (oct->uboot_version[i] == 'm' && 836325618Ssbruno oct->uboot_version[i + 1] == 'i' && 837325618Ssbruno oct->uboot_version[i + 2] == 'p' && 838325618Ssbruno oct->uboot_version[i + 3] == 's') { 839325618Ssbruno oct->uboot_eidx = i - 1; 840325618Ssbruno oct->uboot_version[i - 1] = 0x0; 841325618Ssbruno oct->uboot_len = oct->uboot_eidx - 842325618Ssbruno oct->uboot_sidx + 1; 843325618Ssbruno lio_dev_info(oct, "%s\n", 844325618Ssbruno &oct->uboot_version 845325618Ssbruno [oct->uboot_sidx]); 846325618Ssbruno return; 847325618Ssbruno } 848325618Ssbruno } 849325618Ssbruno } 850325618Ssbruno } 851325618Ssbruno} 852325618Ssbruno 853325618Ssbruno 854325618Ssbruno#define FBUF_SIZE (4 * 1024 * 1024) 855325618Ssbruno 856325618Ssbrunoint 857325618Ssbrunolio_download_firmware(struct octeon_device *oct, const uint8_t * data, 858325618Ssbruno size_t size) 859325618Ssbruno{ 860325618Ssbruno struct lio_firmware_file_header *h; 861325618Ssbruno uint64_t load_addr; 862325618Ssbruno uint32_t crc32_result, i, image_len, rem; 863325618Ssbruno int ret = 0; 864325618Ssbruno 865325618Ssbruno if (size < sizeof(struct lio_firmware_file_header)) { 866325618Ssbruno lio_dev_err(oct, "Firmware file too small (%d < %d).\n", 867325618Ssbruno (uint32_t) size, 868325618Ssbruno (uint32_t) sizeof(struct lio_firmware_file_header)); 869325618Ssbruno return (-EINVAL); 870325618Ssbruno } 871325618Ssbruno 872325618Ssbruno h = __DECONST(struct lio_firmware_file_header *, data); 873325618Ssbruno 874325618Ssbruno if (be32toh(h->magic) != LIO_NIC_MAGIC) { 875325618Ssbruno lio_dev_err(oct, "Unrecognized firmware file.\n"); 876325618Ssbruno return (-EINVAL); 877325618Ssbruno } 878325618Ssbruno 879325618Ssbruno crc32_result = crc32(data, sizeof(struct lio_firmware_file_header) - 880325618Ssbruno sizeof(uint32_t)); 881325618Ssbruno if (crc32_result != be32toh(h->crc32)) { 882325618Ssbruno lio_dev_err(oct, "Firmware CRC mismatch (0x%08x != 0x%08x).\n", 883325618Ssbruno crc32_result, be32toh(h->crc32)); 884325618Ssbruno return (-EINVAL); 885325618Ssbruno } 886325618Ssbruno 887325618Ssbruno if (memcmp(LIO_BASE_VERSION, h->version, 888325618Ssbruno strlen(LIO_BASE_VERSION))) { 889325618Ssbruno lio_dev_err(oct, "Unmatched firmware version. Expected %s.x, got %s.\n", 890325618Ssbruno LIO_BASE_VERSION, h->version); 891325618Ssbruno return (-EINVAL); 892325618Ssbruno } 893325618Ssbruno 894325618Ssbruno if (be32toh(h->num_images) > LIO_MAX_IMAGES) { 895325618Ssbruno lio_dev_err(oct, "Too many images in firmware file (%d).\n", 896325618Ssbruno be32toh(h->num_images)); 897325618Ssbruno return (-EINVAL); 898325618Ssbruno } 899325618Ssbruno 900325618Ssbruno lio_dev_info(oct, "Firmware version: %s\n", h->version); 901325618Ssbruno snprintf(oct->fw_info.lio_firmware_version, 32, "LIQUIDIO: %s", 902325618Ssbruno h->version); 903325618Ssbruno 904325618Ssbruno data += sizeof(struct lio_firmware_file_header); 905325618Ssbruno 906325618Ssbruno lio_dev_info(oct, "Loading %d image(s)\n", be32toh(h->num_images)); 907325618Ssbruno 908325618Ssbruno /* load all images */ 909325618Ssbruno for (i = 0; i < be32toh(h->num_images); i++) { 910325618Ssbruno load_addr = be64toh(h->desc[i].addr); 911325618Ssbruno image_len = be32toh(h->desc[i].len); 912325618Ssbruno 913325618Ssbruno lio_dev_info(oct, "Loading firmware %d at %llx\n", image_len, 914325618Ssbruno (unsigned long long)load_addr); 915325618Ssbruno 916325618Ssbruno /* Write in 4MB chunks */ 917325618Ssbruno rem = image_len; 918325618Ssbruno 919325618Ssbruno while (rem) { 920325618Ssbruno if (rem < FBUF_SIZE) 921325618Ssbruno size = rem; 922325618Ssbruno else 923325618Ssbruno size = FBUF_SIZE; 924325618Ssbruno 925325618Ssbruno /* download the image */ 926325618Ssbruno lio_pci_write_core_mem(oct, load_addr, 927325618Ssbruno __DECONST(uint8_t *, data), 928325618Ssbruno (uint32_t) size); 929325618Ssbruno 930325618Ssbruno data += size; 931325618Ssbruno rem -= (uint32_t) size; 932325618Ssbruno load_addr += size; 933325618Ssbruno } 934325618Ssbruno } 935325618Ssbruno 936325618Ssbruno lio_dev_info(oct, "Writing boot command: %s\n", h->bootcmd); 937325618Ssbruno 938325618Ssbruno /* Invoke the bootcmd */ 939325618Ssbruno ret = lio_console_send_cmd(oct, h->bootcmd, 50); 940325618Ssbruno return (0); 941325618Ssbruno} 942