// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later // Copyright 2022 IBM Corp. #define pr_fmt(fmt) "AST-MCTP: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct mctp *mctp; static struct mctp_binding_astlpc *astlpc; static struct astlpc_ops_data *ops_data; static struct lock mctp_lock = LOCK_UNLOCKED; /* Keyboard Controller Style (KCS) data register address */ #define KCS_DATA_REG 0xca2 /* Keyboard Controller Style (KCS) status register address */ #define KCS_STATUS_REG 0xca3 #define KCS_STATUS_BMC_READY 0x80 #define KCS_STATUS_OBF 0x01 #define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072 #define DESIRED_MTU 32768 #define TX_POLL_MAX 5 /* * The AST LPC binding is described here: * * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-ibm-astlpc.md * * Most of the binding is implemented in libmctp, but we need to provide * accessors for the LPC FW space (for the packet buffer) and for the KCS * peripheral (for the interrupt mechanism). */ struct astlpc_ops_data { uint16_t kcs_data_addr; /* LPC IO space offset for the data register */ uint16_t kcs_stat_addr; /* address of the packet exchange buffer in FW space */ uint32_t lpc_fw_addr; }; static int astlpc_kcs_reg_read(void *binding_data, enum mctp_binding_astlpc_kcs_reg reg, uint8_t *val) { struct astlpc_ops_data *ops_data = binding_data; uint32_t addr; if (reg == MCTP_ASTLPC_KCS_REG_STATUS) addr = ops_data->kcs_stat_addr; else if (reg == MCTP_ASTLPC_KCS_REG_DATA) addr = ops_data->kcs_data_addr; else return OPAL_PARAMETER; *val = lpc_inb(addr); return OPAL_SUCCESS; } static int astlpc_kcs_reg_write(void *binding_data, enum mctp_binding_astlpc_kcs_reg reg, uint8_t val) { struct astlpc_ops_data *ops_data = binding_data; uint32_t addr; prlog(PR_TRACE, "%s 0x%hhx to %s\n", __func__, val, reg ? "status" : "data"); if (reg == MCTP_ASTLPC_KCS_REG_STATUS) addr = ops_data->kcs_stat_addr; else if (reg == MCTP_ASTLPC_KCS_REG_DATA) addr = ops_data->kcs_data_addr; else return OPAL_PARAMETER; lpc_outb(val, addr); return OPAL_SUCCESS; } static int astlpc_read(void *binding_data, void *buf, long offset, size_t len) { struct astlpc_ops_data *ops_data = binding_data; prlog(PR_TRACE, "%s %zu bytes from 0x%lx (lpc: 0x%lx)\n", __func__, len, offset, ops_data->lpc_fw_addr + offset); return lpc_fw_read(ops_data->lpc_fw_addr + offset, buf, len); } static int astlpc_write(void *binding_data, const void *buf, long offset, size_t len) { struct astlpc_ops_data *ops_data = binding_data; prlog(PR_TRACE, "%s %zu bytes to offset 0x%lx (lpc: 0x%lx)\n", __func__, len, offset, ops_data->lpc_fw_addr + offset); return lpc_fw_write(ops_data->lpc_fw_addr + offset, buf, len); } static const struct mctp_binding_astlpc_ops astlpc_ops = { .kcs_read = astlpc_kcs_reg_read, .kcs_write = astlpc_kcs_reg_write, .lpc_read = astlpc_read, .lpc_write = astlpc_write, }; /* we need a poller to crank the mctp state machine during boot */ static void astlpc_poller(void *data) { struct mctp_binding_astlpc *astlpc = (struct mctp_binding_astlpc *)data; if (astlpc) mctp_astlpc_poll(astlpc); } /* at runtime the interrupt should handle it */ static void astlpc_interrupt(uint32_t chip_id __unused, uint32_t irq_msk __unused) { if (astlpc) mctp_astlpc_poll(astlpc); } static struct lpc_client kcs_lpc_client = { .reset = NULL, .interrupt = astlpc_interrupt, }; static void drain_odr(struct astlpc_ops_data *ops_data) { uint8_t kcs_status, kcs_data; uint8_t drain_counter = 255; astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_STATUS, &kcs_status); while (--drain_counter && (kcs_status & KCS_STATUS_OBF)) { astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_DATA, &kcs_data); time_wait_ms(5); astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_STATUS, &kcs_status); } } static int astlpc_binding(void) { struct mctp_bus *bus; int counter = 0; ops_data = zalloc(sizeof(struct astlpc_ops_data)); if (!ops_data) return OPAL_NO_MEM; /* * Current OpenBMC systems put the MCTP buffer 1MB down from * the end of the LPC FW range. * * The size of the FW range is: 0x1000_0000 so the window be at: * * 0x1000_0000 - 2**20 == 0xff00000 */ ops_data->lpc_fw_addr = 0xff00000; /* values chosen by the OpenBMC driver */ ops_data->kcs_data_addr = KCS_DATA_REG; ops_data->kcs_stat_addr = KCS_STATUS_REG; /* Initialise the binding */ astlpc = mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_HOST, DESIRED_MTU, NULL, &astlpc_ops, ops_data); if (!astlpc) { prlog(PR_ERR, "binding init failed\n"); return OPAL_HARDWARE; } /* Read and discard any potentially stale messages in the ODR */ drain_odr(ops_data); /* Register the binding to the LPC bus we are using for this * MCTP configuration. */ if (mctp_register_bus(mctp, mctp_binding_astlpc_core(astlpc), HOST_EID)) { prlog(PR_ERR, "failed to register bus\n"); goto err; } /* lpc/kcs status register poller */ opal_add_poller(astlpc_poller, astlpc); /* Don't start sending messages to the BMC until the bus has * been registered and tx has been enabled */ bus = mctp_binding_astlpc_core(astlpc)->bus; while ((bus == NULL) || (mctp_bus_get_state(bus) == mctp_bus_state_constructed)) { if (++counter >= 1000) { prlog(PR_ERR, "failed to initialize MCTP channel\n"); goto err; } time_wait_ms(5); /* Update bus pointer if it is a nullptr */ if (bus == NULL) bus = mctp_binding_astlpc_core(astlpc)->bus; } return OPAL_SUCCESS; err: mctp_astlpc_destroy(astlpc); free(ops_data); return OPAL_HARDWARE; } static void *mctp_malloc(size_t size) { return malloc(size); } static void mctp_free(void *ptr) { return free(ptr); } static void *mctp_realloc(void *ptr, size_t size) { return realloc(ptr, size); } #ifdef AST_MCTP_DEBUG char buffer[320]; static void mctp_log(int log_lvl, const char *fmt, va_list va) { snprintf(buffer, sizeof(buffer), "%s\n", fmt); vprlog(log_lvl, buffer, va); } #endif int ast_mctp_message_tx(bool tag_owner, uint8_t msg_tag, uint8_t *msg, int msg_len) { unsigned long stop_time; int rc = OPAL_SUCCESS; lock(&mctp_lock); rc = mctp_message_tx(mctp, BMC_EID, tag_owner, msg_tag, msg, msg_len); unlock(&mctp_lock); /* do not poll when we respond to a BMC request */ if (tag_owner) return rc; /* read the Rx_complete command out of the ODR */ stop_time = mftb() + msecs_to_tb(TX_POLL_MAX); while (mftb() < stop_time && !mctp_astlpc_tx_done(astlpc)) mctp_astlpc_poll(astlpc); return rc; } static void message_rx(uint8_t eid, bool tag_owner, uint8_t msg_tag, void *data __unused, void *vmsg, size_t len) { uint8_t *msg = (uint8_t *)vmsg; prlog(PR_TRACE, "message received: msg type: %x, len %zd" " (eid: %d), rx tag %d owner %d\n", *msg, len, eid, tag_owner, msg_tag); /* The first byte defines the type of MCTP packet payload * contained in the message data portion of the MCTP message. * (See DSP0236 for more details about MCTP packet fields). * For now we only support PLDM over MCTP. */ switch (*msg) { case MCTP_MSG_TYPE_PLDM: /* handle the PLDM message */ pldm_mctp_message_rx(eid, tag_owner, msg_tag, msg + sizeof(uint8_t), len - sizeof(uint8_t)); break; default: prlog(PR_ERR, "%s - not a pldm message type (type: %x)\n", __func__, *msg); } } /* * Initialize mctp binding for hbrt and provide interfaces for sending * and receiving mctp messages. */ int ast_mctp_init(void) { uint32_t kcs_serial_irq; struct dt_node *n; /* Search mctp node */ n = dt_find_compatible_node(dt_root, NULL, "mctp"); if (!n) { prlog(PR_ERR, "No MCTP device\n"); return OPAL_PARAMETER; } /* skiboot's malloc/free/realloc are macros so they need * wrappers */ mctp_set_alloc_ops(mctp_malloc, mctp_free, mctp_realloc); /* * /-----\ /---------\ * | bmc | (eid: 8) <- lpc pcie / kcs -> (eid: 9) | skiboot | * \-----/ \---------/ */ mctp = mctp_init(); if (!mctp) { prlog(PR_ERR, "mctp init failed\n"); return OPAL_HARDWARE; } #ifdef AST_MCTP_DEBUG /* Setup the trace hook */ mctp_set_log_custom(mctp_log); #endif /* Set the max message size to be large enough */ mctp_set_max_message_size(mctp, HOST_MAX_INCOMING_MESSAGE_ALLOCATION); /* Setup the message rx callback */ mctp_set_rx_all(mctp, message_rx, NULL); /* Initialize the binding */ if (astlpc_binding()) goto err; /* register an lpc client so we get an interrupt */ kcs_serial_irq = dt_prop_get_u32(n, "interrupts"); kcs_lpc_client.interrupts = LPC_IRQ(kcs_serial_irq); lpc_register_client(dt_get_chip_id(n), &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL); return OPAL_SUCCESS; err: prlog(PR_ERR, "Unable to initialize MCTP\n"); mctp_destroy(mctp); mctp = NULL; return OPAL_HARDWARE; } void ast_mctp_exit(void) { if (astlpc) { mctp_astlpc_destroy(astlpc); astlpc = NULL; } if (mctp) { mctp_destroy(mctp); mctp = NULL; } }