13d36ee2eSJeremy Kerr /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2672c8852SJeremy Kerr 3672c8852SJeremy Kerr #include <assert.h> 4672c8852SJeremy Kerr #include <fcntl.h> 5672c8852SJeremy Kerr #include <stdbool.h> 6672c8852SJeremy Kerr #include <stdlib.h> 7672c8852SJeremy Kerr #include <string.h> 8672c8852SJeremy Kerr #include <unistd.h> 9672c8852SJeremy Kerr 10672c8852SJeremy Kerr #include <sys/ioctl.h> 11672c8852SJeremy Kerr #include <sys/mman.h> 12672c8852SJeremy Kerr 13672c8852SJeremy Kerr #include <linux/aspeed-lpc-ctrl.h> 14672c8852SJeremy Kerr 15672c8852SJeremy Kerr #define pr_fmt(x) "astlpc: " x 16672c8852SJeremy Kerr 17672c8852SJeremy Kerr #include "libmctp.h" 18672c8852SJeremy Kerr #include "libmctp-alloc.h" 19672c8852SJeremy Kerr #include "libmctp-log.h" 20672c8852SJeremy Kerr #include "libmctp-astlpc.h" 21672c8852SJeremy Kerr 22672c8852SJeremy Kerr struct mctp_binding_astlpc { 23672c8852SJeremy Kerr struct mctp_binding binding; 24672c8852SJeremy Kerr void *lpc_map_base; 25672c8852SJeremy Kerr union { 26672c8852SJeremy Kerr void *lpc_map; 27672c8852SJeremy Kerr struct mctp_lpcmap_hdr *lpc_hdr; 28672c8852SJeremy Kerr }; 29672c8852SJeremy Kerr int kcs_fd; 30672c8852SJeremy Kerr uint8_t kcs_status; 31672c8852SJeremy Kerr 32672c8852SJeremy Kerr bool running; 33672c8852SJeremy Kerr 34672c8852SJeremy Kerr /* temporary transmit buffer */ 35672c8852SJeremy Kerr uint8_t txbuf[256]; 36672c8852SJeremy Kerr }; 37672c8852SJeremy Kerr 38672c8852SJeremy Kerr #ifndef container_of 39672c8852SJeremy Kerr #define container_of(ptr, type, member) \ 40672c8852SJeremy Kerr (type *)((char *)(ptr) - (char *)&((type *)0)->member) 41672c8852SJeremy Kerr #endif 42672c8852SJeremy Kerr 43672c8852SJeremy Kerr #define binding_to_astlpc(b) \ 44672c8852SJeremy Kerr container_of(b, struct mctp_binding_astlpc, binding) 45672c8852SJeremy Kerr 46672c8852SJeremy Kerr #define MCTP_MAGIC 0x4d435450 47672c8852SJeremy Kerr #define BMC_VER_MIN 1 48672c8852SJeremy Kerr #define BMC_VER_CUR 1 49672c8852SJeremy Kerr 50672c8852SJeremy Kerr struct mctp_lpcmap_hdr { 51672c8852SJeremy Kerr uint32_t magic; 52672c8852SJeremy Kerr 53672c8852SJeremy Kerr uint16_t bmc_ver_min; 54672c8852SJeremy Kerr uint16_t bmc_ver_cur; 55672c8852SJeremy Kerr uint16_t host_ver_min; 56672c8852SJeremy Kerr uint16_t host_ver_cur; 57672c8852SJeremy Kerr uint16_t negotiated_ver; 58672c8852SJeremy Kerr uint16_t pad0; 59672c8852SJeremy Kerr 60672c8852SJeremy Kerr uint32_t rx_offset; 61672c8852SJeremy Kerr uint32_t rx_size; 62672c8852SJeremy Kerr uint32_t tx_offset; 63672c8852SJeremy Kerr uint32_t tx_size; 64672c8852SJeremy Kerr } __attribute__((packed)); 65672c8852SJeremy Kerr 66672c8852SJeremy Kerr /* layout of TX/RX areas */ 67672c8852SJeremy Kerr static const uint32_t rx_offset = 0x100; 68672c8852SJeremy Kerr static const uint32_t rx_size = 0x100; 69672c8852SJeremy Kerr static const uint32_t tx_offset = 0x200; 70672c8852SJeremy Kerr static const uint32_t tx_size = 0x100; 71672c8852SJeremy Kerr 72672c8852SJeremy Kerr /* kernel interface */ 73672c8852SJeremy Kerr static const char *kcs_path = "/dev/mctp0"; 74672c8852SJeremy Kerr static const char *lpc_path = "/dev/aspeed-lpc-ctrl"; 75672c8852SJeremy Kerr 76672c8852SJeremy Kerr #define LPC_WIN_SIZE (1 * 1024 * 1024) 77672c8852SJeremy Kerr 78672c8852SJeremy Kerr enum { 79672c8852SJeremy Kerr KCS_REG_DATA = 0, 80672c8852SJeremy Kerr KCS_REG_STATUS = 1, 81672c8852SJeremy Kerr }; 82672c8852SJeremy Kerr 83672c8852SJeremy Kerr #define KCS_STATUS_BMC_READY 0x80 84672c8852SJeremy Kerr #define KCS_STATUS_CHANNEL_ACTIVE 0x40 85672c8852SJeremy Kerr #define KCS_STATUS_IBF 0x02 86672c8852SJeremy Kerr #define KCS_STATUS_OBF 0x01 87672c8852SJeremy Kerr 88672c8852SJeremy Kerr static int mctp_astlpc_kcs_set_status(struct mctp_binding_astlpc *astlpc, 89672c8852SJeremy Kerr uint8_t status) 90672c8852SJeremy Kerr { 91*1a4b55acSJeremy Kerr uint8_t buf[2]; 92672c8852SJeremy Kerr int rc; 93672c8852SJeremy Kerr 94*1a4b55acSJeremy Kerr /* Since we're setting the status register, we want the other endpoint 95*1a4b55acSJeremy Kerr * to be interrupted. However, some hardware may only raise a host-side 96*1a4b55acSJeremy Kerr * interrupt on an ODR event. 97*1a4b55acSJeremy Kerr * So, write a dummy value of 0xff to ODR, which will ensure that an 98*1a4b55acSJeremy Kerr * interrupt is triggered, and can be ignored by the host. 99*1a4b55acSJeremy Kerr */ 100*1a4b55acSJeremy Kerr buf[KCS_REG_DATA] = 0xff; 101*1a4b55acSJeremy Kerr buf[KCS_REG_STATUS] = status; 102*1a4b55acSJeremy Kerr 103*1a4b55acSJeremy Kerr rc = pwrite(astlpc->kcs_fd, buf, 2, 0); 104672c8852SJeremy Kerr if (rc != 1) { 105672c8852SJeremy Kerr mctp_prwarn("KCS status write failed"); 106672c8852SJeremy Kerr return -1; 107672c8852SJeremy Kerr } 108672c8852SJeremy Kerr return 0; 109672c8852SJeremy Kerr } 110672c8852SJeremy Kerr 111672c8852SJeremy Kerr static int mctp_astlpc_kcs_send(struct mctp_binding_astlpc *astlpc, 112672c8852SJeremy Kerr uint8_t data) 113672c8852SJeremy Kerr { 114672c8852SJeremy Kerr uint8_t status; 115672c8852SJeremy Kerr int rc; 116672c8852SJeremy Kerr 117672c8852SJeremy Kerr for (;;) { 118672c8852SJeremy Kerr rc = pread(astlpc->kcs_fd, &status, 1, KCS_REG_STATUS); 119672c8852SJeremy Kerr if (rc != 1) { 120672c8852SJeremy Kerr mctp_prwarn("KCE status read failed"); 121672c8852SJeremy Kerr return -1; 122672c8852SJeremy Kerr } 123672c8852SJeremy Kerr if (!(status & KCS_STATUS_OBF)) 124672c8852SJeremy Kerr break; 125672c8852SJeremy Kerr /* todo: timeout */ 126672c8852SJeremy Kerr } 127672c8852SJeremy Kerr 128672c8852SJeremy Kerr rc = pwrite(astlpc->kcs_fd, &data, 1, KCS_REG_DATA); 129672c8852SJeremy Kerr if (rc != 1) { 130672c8852SJeremy Kerr mctp_prwarn("KCS data write failed"); 131672c8852SJeremy Kerr return -1; 132672c8852SJeremy Kerr } 133672c8852SJeremy Kerr 134672c8852SJeremy Kerr return 0; 135672c8852SJeremy Kerr } 136672c8852SJeremy Kerr 137672c8852SJeremy Kerr static int mctp_binding_astlpc_tx(struct mctp_binding *b, 138672c8852SJeremy Kerr struct mctp_pktbuf *pkt) 139672c8852SJeremy Kerr { 140672c8852SJeremy Kerr struct mctp_binding_astlpc *astlpc = binding_to_astlpc(b); 141672c8852SJeremy Kerr uint32_t len; 142672c8852SJeremy Kerr 143672c8852SJeremy Kerr len = mctp_pktbuf_size(pkt); 144672c8852SJeremy Kerr if (len > rx_size - 4) { 145672c8852SJeremy Kerr mctp_prwarn("invalid TX len 0x%x", len); 146672c8852SJeremy Kerr return -1; 147672c8852SJeremy Kerr } 148672c8852SJeremy Kerr 149672c8852SJeremy Kerr *(uint32_t *)(astlpc->lpc_map + rx_offset) = htobe32(len); 150672c8852SJeremy Kerr 151672c8852SJeremy Kerr memcpy(astlpc->lpc_map + rx_offset + 4, mctp_pktbuf_hdr(pkt), len); 152672c8852SJeremy Kerr 153672c8852SJeremy Kerr mctp_binding_set_tx_enabled(b, false); 154672c8852SJeremy Kerr 155672c8852SJeremy Kerr mctp_astlpc_kcs_send(astlpc, 0x1); 156672c8852SJeremy Kerr return 0; 157672c8852SJeremy Kerr } 158672c8852SJeremy Kerr 159672c8852SJeremy Kerr static void mctp_astlpc_init_channel(struct mctp_binding_astlpc *astlpc) 160672c8852SJeremy Kerr { 161672c8852SJeremy Kerr /* todo: actual version negotiation */ 162672c8852SJeremy Kerr astlpc->lpc_hdr->negotiated_ver = htobe16(1); 163672c8852SJeremy Kerr mctp_astlpc_kcs_set_status(astlpc, 164672c8852SJeremy Kerr KCS_STATUS_BMC_READY | KCS_STATUS_CHANNEL_ACTIVE | 165672c8852SJeremy Kerr KCS_STATUS_OBF); 166672c8852SJeremy Kerr 167672c8852SJeremy Kerr mctp_binding_set_tx_enabled(&astlpc->binding, true); 168672c8852SJeremy Kerr } 169672c8852SJeremy Kerr 170672c8852SJeremy Kerr static void mctp_astlpc_rx_start(struct mctp_binding_astlpc *astlpc) 171672c8852SJeremy Kerr { 172672c8852SJeremy Kerr struct mctp_pktbuf *pkt; 173672c8852SJeremy Kerr uint32_t len; 174672c8852SJeremy Kerr 175672c8852SJeremy Kerr len = htobe32(*(uint32_t *)(astlpc->lpc_map + tx_offset)); 176672c8852SJeremy Kerr if (len > tx_size - 4) { 177672c8852SJeremy Kerr mctp_prwarn("invalid RX len 0x%x", len); 178672c8852SJeremy Kerr return; 179672c8852SJeremy Kerr } 180672c8852SJeremy Kerr 181672c8852SJeremy Kerr if (len > MCTP_MTU + sizeof(struct mctp_hdr)) { 182672c8852SJeremy Kerr mctp_prwarn("invalid RX len 0x%x", len); 183672c8852SJeremy Kerr return; 184672c8852SJeremy Kerr } 185672c8852SJeremy Kerr 186672c8852SJeremy Kerr pkt = mctp_pktbuf_alloc(len); 187672c8852SJeremy Kerr if (!pkt) 188672c8852SJeremy Kerr goto out_complete; 189672c8852SJeremy Kerr 190672c8852SJeremy Kerr memcpy(mctp_pktbuf_hdr(pkt), astlpc->lpc_map + tx_offset + 4, len); 191672c8852SJeremy Kerr 192672c8852SJeremy Kerr mctp_bus_rx(&astlpc->binding, pkt); 193672c8852SJeremy Kerr 194672c8852SJeremy Kerr out_complete: 195672c8852SJeremy Kerr mctp_astlpc_kcs_send(astlpc, 0x2); 196672c8852SJeremy Kerr } 197672c8852SJeremy Kerr 198672c8852SJeremy Kerr static void mctp_astlpc_tx_complete(struct mctp_binding_astlpc *astlpc) 199672c8852SJeremy Kerr { 200672c8852SJeremy Kerr mctp_binding_set_tx_enabled(&astlpc->binding, true); 201672c8852SJeremy Kerr } 202672c8852SJeremy Kerr 203672c8852SJeremy Kerr int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc) 204672c8852SJeremy Kerr { 205672c8852SJeremy Kerr uint8_t kcs_regs[2], data; 206672c8852SJeremy Kerr int rc; 207672c8852SJeremy Kerr 208672c8852SJeremy Kerr rc = pread(astlpc->kcs_fd, kcs_regs, 2, 0); 209672c8852SJeremy Kerr if (rc < 0) { 210672c8852SJeremy Kerr mctp_prwarn("KCS read error"); 211672c8852SJeremy Kerr return -1; 212672c8852SJeremy Kerr } else if (rc != 2) { 213672c8852SJeremy Kerr mctp_prwarn("KCS short read (%d)", rc); 214672c8852SJeremy Kerr return -1; 215672c8852SJeremy Kerr } 216672c8852SJeremy Kerr 217672c8852SJeremy Kerr if (!(kcs_regs[KCS_REG_STATUS] & KCS_STATUS_IBF)) 218672c8852SJeremy Kerr return 0; 219672c8852SJeremy Kerr 220672c8852SJeremy Kerr data = kcs_regs[KCS_REG_DATA]; 221672c8852SJeremy Kerr switch (data) { 222672c8852SJeremy Kerr case 0x0: 223672c8852SJeremy Kerr mctp_astlpc_init_channel(astlpc); 224672c8852SJeremy Kerr break; 225672c8852SJeremy Kerr case 0x1: 226672c8852SJeremy Kerr mctp_astlpc_rx_start(astlpc); 227672c8852SJeremy Kerr break; 228672c8852SJeremy Kerr case 0x2: 229672c8852SJeremy Kerr mctp_astlpc_tx_complete(astlpc); 230672c8852SJeremy Kerr break; 231*1a4b55acSJeremy Kerr case 0xff: 232*1a4b55acSJeremy Kerr /* reserved value for dummy data writes; do nothing */ 233*1a4b55acSJeremy Kerr break; 234672c8852SJeremy Kerr default: 235672c8852SJeremy Kerr mctp_prwarn("unknown message 0x%x", data); 236672c8852SJeremy Kerr } 237672c8852SJeremy Kerr return 0; 238672c8852SJeremy Kerr } 239672c8852SJeremy Kerr 240672c8852SJeremy Kerr int mctp_astlpc_get_fd(struct mctp_binding_astlpc *astlpc) 241672c8852SJeremy Kerr { 242672c8852SJeremy Kerr return astlpc->kcs_fd; 243672c8852SJeremy Kerr } 244672c8852SJeremy Kerr 245672c8852SJeremy Kerr 246672c8852SJeremy Kerr void mctp_astlpc_register_bus(struct mctp_binding_astlpc *astlpc, 247672c8852SJeremy Kerr struct mctp *mctp, mctp_eid_t eid) 248672c8852SJeremy Kerr { 249672c8852SJeremy Kerr mctp_register_bus(mctp, &astlpc->binding, eid); 250672c8852SJeremy Kerr } 251672c8852SJeremy Kerr 252672c8852SJeremy Kerr static int mctp_astlpc_init_bmc(struct mctp_binding_astlpc *astlpc) 253672c8852SJeremy Kerr { 254672c8852SJeremy Kerr uint8_t status; 255672c8852SJeremy Kerr int rc; 256672c8852SJeremy Kerr 257672c8852SJeremy Kerr astlpc->lpc_hdr->magic = htobe32(MCTP_MAGIC); 258672c8852SJeremy Kerr astlpc->lpc_hdr->bmc_ver_min = htobe16(BMC_VER_MIN); 259672c8852SJeremy Kerr astlpc->lpc_hdr->bmc_ver_cur = htobe16(BMC_VER_CUR); 260672c8852SJeremy Kerr 261672c8852SJeremy Kerr astlpc->lpc_hdr->rx_offset = htobe32(rx_offset); 262672c8852SJeremy Kerr astlpc->lpc_hdr->rx_size = htobe32(rx_size); 263672c8852SJeremy Kerr astlpc->lpc_hdr->tx_offset = htobe32(tx_offset); 264672c8852SJeremy Kerr astlpc->lpc_hdr->tx_size = htobe32(tx_size); 265672c8852SJeremy Kerr 266672c8852SJeremy Kerr /* set status indicating that the BMC is now active */ 267672c8852SJeremy Kerr status = KCS_STATUS_BMC_READY | KCS_STATUS_OBF; 268672c8852SJeremy Kerr rc = pwrite(astlpc->kcs_fd, &status, 1, KCS_REG_STATUS); 269672c8852SJeremy Kerr if (rc != 1) { 270672c8852SJeremy Kerr mctp_prwarn("KCS write failed"); 271672c8852SJeremy Kerr rc = -1; 272672c8852SJeremy Kerr } else { 273672c8852SJeremy Kerr rc = 0; 274672c8852SJeremy Kerr } 275672c8852SJeremy Kerr 276672c8852SJeremy Kerr return rc; 277672c8852SJeremy Kerr } 278672c8852SJeremy Kerr 279672c8852SJeremy Kerr static int mctp_astlpc_init_lpc(struct mctp_binding_astlpc *astlpc) 280672c8852SJeremy Kerr { 281672c8852SJeremy Kerr struct aspeed_lpc_ctrl_mapping map = { 282672c8852SJeremy Kerr .window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY, 283672c8852SJeremy Kerr .window_id = 0, /* There's only one */ 284672c8852SJeremy Kerr .flags = 0, 285672c8852SJeremy Kerr .addr = 0, 286672c8852SJeremy Kerr .offset = 0, 287672c8852SJeremy Kerr .size = 0 288672c8852SJeremy Kerr }; 289672c8852SJeremy Kerr int fd, rc; 290672c8852SJeremy Kerr 291672c8852SJeremy Kerr fd = open(lpc_path, O_RDWR | O_SYNC); 292672c8852SJeremy Kerr if (fd < 0) { 293672c8852SJeremy Kerr mctp_prwarn("LPC open (%s) failed", lpc_path); 294672c8852SJeremy Kerr return -1; 295672c8852SJeremy Kerr } 296672c8852SJeremy Kerr 297672c8852SJeremy Kerr rc = ioctl(fd, ASPEED_LPC_CTRL_IOCTL_GET_SIZE, &map); 298672c8852SJeremy Kerr if (rc) { 299672c8852SJeremy Kerr mctp_prwarn("LPC GET_SIZE failed"); 300672c8852SJeremy Kerr close(fd); 301672c8852SJeremy Kerr return -1; 302672c8852SJeremy Kerr } 303672c8852SJeremy Kerr 304672c8852SJeremy Kerr astlpc->lpc_map_base = mmap(NULL, map.size, PROT_READ | PROT_WRITE, 305672c8852SJeremy Kerr MAP_SHARED, fd, 0); 306672c8852SJeremy Kerr if (astlpc->lpc_map_base == MAP_FAILED) { 307672c8852SJeremy Kerr mctp_prwarn("LPC mmap failed"); 308672c8852SJeremy Kerr rc = -1; 309672c8852SJeremy Kerr } else { 310672c8852SJeremy Kerr astlpc->lpc_map = astlpc->lpc_map_base + 311672c8852SJeremy Kerr map.size - LPC_WIN_SIZE; 312672c8852SJeremy Kerr } 313672c8852SJeremy Kerr 314672c8852SJeremy Kerr close(fd); 315672c8852SJeremy Kerr 316672c8852SJeremy Kerr return rc; 317672c8852SJeremy Kerr } 318672c8852SJeremy Kerr 319672c8852SJeremy Kerr static int mctp_astlpc_init_kcs(struct mctp_binding_astlpc *astlpc) 320672c8852SJeremy Kerr { 321672c8852SJeremy Kerr astlpc->kcs_fd = open(kcs_path, O_RDWR); 322672c8852SJeremy Kerr if (astlpc->kcs_fd < 0) 323672c8852SJeremy Kerr return -1; 324672c8852SJeremy Kerr 325672c8852SJeremy Kerr return 0; 326672c8852SJeremy Kerr } 327672c8852SJeremy Kerr 328672c8852SJeremy Kerr struct mctp_binding_astlpc *mctp_astlpc_init(void) 329672c8852SJeremy Kerr { 330672c8852SJeremy Kerr struct mctp_binding_astlpc *astlpc; 331672c8852SJeremy Kerr int rc; 332672c8852SJeremy Kerr 333672c8852SJeremy Kerr astlpc = __mctp_alloc(sizeof(*astlpc)); 334672c8852SJeremy Kerr memset(astlpc, 0, sizeof(*astlpc)); 335672c8852SJeremy Kerr astlpc->binding.name = "astlpc"; 336672c8852SJeremy Kerr astlpc->binding.version = 1; 337672c8852SJeremy Kerr astlpc->binding.tx = mctp_binding_astlpc_tx; 338672c8852SJeremy Kerr 339672c8852SJeremy Kerr rc = mctp_astlpc_init_lpc(astlpc); 340672c8852SJeremy Kerr if (rc) { 341672c8852SJeremy Kerr free(astlpc); 342672c8852SJeremy Kerr return NULL; 343672c8852SJeremy Kerr } 344672c8852SJeremy Kerr 345672c8852SJeremy Kerr rc = mctp_astlpc_init_kcs(astlpc); 346672c8852SJeremy Kerr if (rc) { 347672c8852SJeremy Kerr free(astlpc); 348672c8852SJeremy Kerr return NULL; 349672c8852SJeremy Kerr } 350672c8852SJeremy Kerr 351672c8852SJeremy Kerr rc = mctp_astlpc_init_bmc(astlpc); 352672c8852SJeremy Kerr if (rc) { 353672c8852SJeremy Kerr free(astlpc); 354672c8852SJeremy Kerr return NULL; 355672c8852SJeremy Kerr } 356672c8852SJeremy Kerr 357672c8852SJeremy Kerr return astlpc; 358672c8852SJeremy Kerr } 359672c8852SJeremy Kerr 360