1 /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 3 #include <assert.h> 4 #include <endian.h> 5 #include <err.h> 6 #include <stdbool.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #define pr_fmt(x) "astlpc: " x 11 12 #if HAVE_CONFIG_H 13 #include "config.h" 14 #endif 15 16 #include "libmctp.h" 17 #include "libmctp-alloc.h" 18 #include "libmctp-log.h" 19 #include "libmctp-astlpc.h" 20 21 #ifdef MCTP_HAVE_FILEIO 22 23 #include <unistd.h> 24 #include <fcntl.h> 25 #include <sys/ioctl.h> 26 #include <sys/mman.h> 27 #include <linux/aspeed-lpc-ctrl.h> 28 29 /* kernel interface */ 30 static const char *kcs_path = "/dev/mctp0"; 31 static const char *lpc_path = "/dev/aspeed-lpc-ctrl"; 32 33 #endif 34 35 struct mctp_binding_astlpc { 36 struct mctp_binding binding; 37 38 union { 39 void *lpc_map; 40 struct mctp_lpcmap_hdr *lpc_hdr; 41 }; 42 43 /* direct ops data */ 44 struct mctp_binding_astlpc_ops ops; 45 void *ops_data; 46 struct mctp_lpcmap_hdr *priv_hdr; 47 48 /* fileio ops data */ 49 void *lpc_map_base; 50 int kcs_fd; 51 uint8_t kcs_status; 52 53 bool running; 54 55 /* temporary transmit buffer */ 56 uint8_t txbuf[256]; 57 }; 58 59 #ifndef container_of 60 #define container_of(ptr, type, member) \ 61 (type *)((char *)(ptr) - (char *)&((type *)0)->member) 62 #endif 63 64 #define binding_to_astlpc(b) \ 65 container_of(b, struct mctp_binding_astlpc, binding) 66 67 #define MCTP_MAGIC 0x4d435450 68 #define BMC_VER_MIN 1 69 #define BMC_VER_CUR 1 70 71 struct mctp_lpcmap_hdr { 72 uint32_t magic; 73 74 uint16_t bmc_ver_min; 75 uint16_t bmc_ver_cur; 76 uint16_t host_ver_min; 77 uint16_t host_ver_cur; 78 uint16_t negotiated_ver; 79 uint16_t pad0; 80 81 uint32_t rx_offset; 82 uint32_t rx_size; 83 uint32_t tx_offset; 84 uint32_t tx_size; 85 } __attribute__((packed)); 86 87 /* layout of TX/RX areas */ 88 static const uint32_t rx_offset = 0x100; 89 static const uint32_t rx_size = 0x100; 90 static const uint32_t tx_offset = 0x200; 91 static const uint32_t tx_size = 0x100; 92 93 #define LPC_WIN_SIZE (1 * 1024 * 1024) 94 95 enum { 96 KCS_REG_DATA = 0, 97 KCS_REG_STATUS = 1, 98 }; 99 100 #define KCS_STATUS_BMC_READY 0x80 101 #define KCS_STATUS_CHANNEL_ACTIVE 0x40 102 #define KCS_STATUS_IBF 0x02 103 #define KCS_STATUS_OBF 0x01 104 105 static bool lpc_direct(struct mctp_binding_astlpc *astlpc) 106 { 107 return astlpc->lpc_map != NULL; 108 } 109 110 static int mctp_astlpc_kcs_set_status(struct mctp_binding_astlpc *astlpc, 111 uint8_t status) 112 { 113 uint8_t data; 114 int rc; 115 116 /* Since we're setting the status register, we want the other endpoint 117 * to be interrupted. However, some hardware may only raise a host-side 118 * interrupt on an ODR event. 119 * So, write a dummy value of 0xff to ODR, which will ensure that an 120 * interrupt is triggered, and can be ignored by the host. 121 */ 122 data = 0xff; 123 status |= KCS_STATUS_OBF; 124 125 rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA, 126 data); 127 if (rc) { 128 mctp_prwarn("KCS dummy data write failed"); 129 return -1; 130 } 131 132 133 rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_STATUS, 134 status); 135 if (rc) { 136 mctp_prwarn("KCS status write failed"); 137 return -1; 138 } 139 return 0; 140 } 141 142 static int mctp_astlpc_kcs_send(struct mctp_binding_astlpc *astlpc, 143 uint8_t data) 144 { 145 uint8_t status; 146 int rc; 147 148 for (;;) { 149 rc = astlpc->ops.kcs_read(astlpc->ops_data, 150 MCTP_ASTLPC_KCS_REG_STATUS, &status); 151 if (rc) { 152 mctp_prwarn("KCS status read failed"); 153 return -1; 154 } 155 if (!(status & KCS_STATUS_OBF)) 156 break; 157 /* todo: timeout */ 158 } 159 160 rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA, 161 data); 162 if (rc) { 163 mctp_prwarn("KCS data write failed"); 164 return -1; 165 } 166 167 return 0; 168 } 169 170 static int mctp_binding_astlpc_tx(struct mctp_binding *b, 171 struct mctp_pktbuf *pkt) 172 { 173 struct mctp_binding_astlpc *astlpc = binding_to_astlpc(b); 174 uint32_t len; 175 176 len = mctp_pktbuf_size(pkt); 177 if (len > rx_size - 4) { 178 mctp_prwarn("invalid TX len 0x%x", len); 179 return -1; 180 } 181 182 if (lpc_direct(astlpc)) { 183 *(uint32_t *)(astlpc->lpc_map + rx_offset) = htobe32(len); 184 memcpy(astlpc->lpc_map + rx_offset + 4, mctp_pktbuf_hdr(pkt), 185 len); 186 } else { 187 uint32_t tmp = htobe32(len); 188 astlpc->ops.lpc_write(astlpc->ops_data, &tmp, rx_offset, 189 sizeof(tmp)); 190 astlpc->ops.lpc_write(astlpc->ops_data, mctp_pktbuf_hdr(pkt), 191 rx_offset + 4, len); 192 } 193 194 mctp_binding_set_tx_enabled(b, false); 195 196 mctp_astlpc_kcs_send(astlpc, 0x1); 197 return 0; 198 } 199 200 static void mctp_astlpc_init_channel(struct mctp_binding_astlpc *astlpc) 201 { 202 /* todo: actual version negotiation */ 203 if (lpc_direct(astlpc)) { 204 astlpc->lpc_hdr->negotiated_ver = htobe16(1); 205 } else { 206 uint16_t ver = htobe16(1); 207 astlpc->ops.lpc_write(astlpc->ops_data, &ver, 208 offsetof(struct mctp_lpcmap_hdr, 209 negotiated_ver), 210 sizeof(ver)); 211 } 212 mctp_astlpc_kcs_set_status(astlpc, 213 KCS_STATUS_BMC_READY | KCS_STATUS_CHANNEL_ACTIVE | 214 KCS_STATUS_OBF); 215 216 mctp_binding_set_tx_enabled(&astlpc->binding, true); 217 } 218 219 static void mctp_astlpc_rx_start(struct mctp_binding_astlpc *astlpc) 220 { 221 struct mctp_pktbuf *pkt; 222 uint32_t len; 223 224 if (lpc_direct(astlpc)) { 225 len = *(uint32_t *)(astlpc->lpc_map + tx_offset); 226 } else { 227 astlpc->ops.lpc_read(astlpc->ops_data, &len, 228 tx_offset, sizeof(len)); 229 } 230 len = be32toh(len); 231 232 if (len > tx_size - 4) { 233 mctp_prwarn("invalid RX len 0x%x", len); 234 return; 235 } 236 237 if (len > astlpc->binding.pkt_size) { 238 mctp_prwarn("invalid RX len 0x%x", len); 239 return; 240 } 241 242 pkt = mctp_pktbuf_alloc(&astlpc->binding, len); 243 if (!pkt) 244 goto out_complete; 245 246 if (lpc_direct(astlpc)) { 247 memcpy(mctp_pktbuf_hdr(pkt), 248 astlpc->lpc_map + tx_offset + 4, len); 249 } else { 250 astlpc->ops.lpc_read(astlpc->ops_data, mctp_pktbuf_hdr(pkt), 251 tx_offset + 4, len); 252 } 253 254 mctp_bus_rx(&astlpc->binding, pkt); 255 256 out_complete: 257 mctp_astlpc_kcs_send(astlpc, 0x2); 258 } 259 260 static void mctp_astlpc_tx_complete(struct mctp_binding_astlpc *astlpc) 261 { 262 mctp_binding_set_tx_enabled(&astlpc->binding, true); 263 } 264 265 int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc) 266 { 267 uint8_t status, data; 268 int rc; 269 270 rc = astlpc->ops.kcs_read(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_STATUS, 271 &status); 272 if (rc) { 273 mctp_prwarn("KCS read error"); 274 return -1; 275 } 276 277 if (!(status & KCS_STATUS_IBF)) 278 return 0; 279 280 rc = astlpc->ops.kcs_read(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA, 281 &data); 282 if (rc) { 283 mctp_prwarn("KCS data read error"); 284 return -1; 285 } 286 287 switch (data) { 288 case 0x0: 289 mctp_astlpc_init_channel(astlpc); 290 break; 291 case 0x1: 292 mctp_astlpc_rx_start(astlpc); 293 break; 294 case 0x2: 295 mctp_astlpc_tx_complete(astlpc); 296 break; 297 case 0xff: 298 /* reserved value for dummy data writes; do nothing */ 299 break; 300 default: 301 mctp_prwarn("unknown message 0x%x", data); 302 } 303 return 0; 304 } 305 306 static int mctp_astlpc_init_bmc(struct mctp_binding_astlpc *astlpc) 307 { 308 struct mctp_lpcmap_hdr *hdr; 309 uint8_t status; 310 int rc; 311 312 if (lpc_direct(astlpc)) 313 hdr = astlpc->lpc_hdr; 314 else 315 hdr = astlpc->priv_hdr; 316 317 hdr->magic = htobe32(MCTP_MAGIC); 318 hdr->bmc_ver_min = htobe16(BMC_VER_MIN); 319 hdr->bmc_ver_cur = htobe16(BMC_VER_CUR); 320 321 hdr->rx_offset = htobe32(rx_offset); 322 hdr->rx_size = htobe32(rx_size); 323 hdr->tx_offset = htobe32(tx_offset); 324 hdr->tx_size = htobe32(tx_size); 325 326 if (!lpc_direct(astlpc)) 327 astlpc->ops.lpc_write(astlpc->ops_data, hdr, 0, sizeof(*hdr)); 328 329 /* set status indicating that the BMC is now active */ 330 status = KCS_STATUS_BMC_READY | KCS_STATUS_OBF; 331 rc = astlpc->ops.kcs_write(astlpc->ops_data, 332 MCTP_ASTLPC_KCS_REG_STATUS, status); 333 if (rc) { 334 mctp_prwarn("KCS write failed"); 335 } 336 337 return rc; 338 } 339 340 static int mctp_binding_astlpc_start(struct mctp_binding *b) 341 { 342 struct mctp_binding_astlpc *astlpc = container_of(b, 343 struct mctp_binding_astlpc, binding); 344 345 return mctp_astlpc_init_bmc(astlpc); 346 } 347 348 /* allocate and basic initialisation */ 349 static struct mctp_binding_astlpc *__mctp_astlpc_init(void) 350 { 351 struct mctp_binding_astlpc *astlpc; 352 353 astlpc = __mctp_alloc(sizeof(*astlpc)); 354 memset(astlpc, 0, sizeof(*astlpc)); 355 astlpc->binding.name = "astlpc"; 356 astlpc->binding.version = 1; 357 astlpc->binding.tx = mctp_binding_astlpc_tx; 358 astlpc->binding.start = mctp_binding_astlpc_start; 359 astlpc->binding.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU); 360 astlpc->binding.pkt_pad = 0; 361 astlpc->lpc_map = NULL; 362 363 return astlpc; 364 } 365 366 struct mctp_binding *mctp_binding_astlpc_core(struct mctp_binding_astlpc *b) 367 { 368 return &b->binding; 369 } 370 371 struct mctp_binding_astlpc *mctp_astlpc_init_ops( 372 struct mctp_binding_astlpc_ops *ops, 373 void *ops_data, void *lpc_map) 374 { 375 struct mctp_binding_astlpc *astlpc; 376 377 astlpc = __mctp_astlpc_init(); 378 if (!astlpc) 379 return NULL; 380 381 memcpy(&astlpc->ops, ops, sizeof(astlpc->ops)); 382 astlpc->ops_data = ops_data; 383 astlpc->lpc_map = lpc_map; 384 385 /* In indirect mode, we keep a separate buffer of header data. 386 * We need to sync this through the lpc_read/lpc_write ops. 387 */ 388 if (!astlpc->lpc_map) 389 astlpc->priv_hdr = __mctp_alloc(sizeof(*astlpc->priv_hdr)); 390 391 return astlpc; 392 } 393 394 #ifdef MCTP_HAVE_FILEIO 395 static int mctp_astlpc_init_fileio_lpc(struct mctp_binding_astlpc *astlpc) 396 { 397 struct aspeed_lpc_ctrl_mapping map = { 398 .window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY, 399 .window_id = 0, /* There's only one */ 400 .flags = 0, 401 .addr = 0, 402 .offset = 0, 403 .size = 0 404 }; 405 int fd, rc; 406 407 fd = open(lpc_path, O_RDWR | O_SYNC); 408 if (fd < 0) { 409 mctp_prwarn("LPC open (%s) failed", lpc_path); 410 return -1; 411 } 412 413 rc = ioctl(fd, ASPEED_LPC_CTRL_IOCTL_GET_SIZE, &map); 414 if (rc) { 415 mctp_prwarn("LPC GET_SIZE failed"); 416 close(fd); 417 return -1; 418 } 419 420 astlpc->lpc_map_base = mmap(NULL, map.size, PROT_READ | PROT_WRITE, 421 MAP_SHARED, fd, 0); 422 if (astlpc->lpc_map_base == MAP_FAILED) { 423 mctp_prwarn("LPC mmap failed"); 424 rc = -1; 425 } else { 426 astlpc->lpc_map = astlpc->lpc_map_base + 427 map.size - LPC_WIN_SIZE; 428 } 429 430 close(fd); 431 432 return rc; 433 } 434 435 static int mctp_astlpc_init_fileio_kcs(struct mctp_binding_astlpc *astlpc) 436 { 437 astlpc->kcs_fd = open(kcs_path, O_RDWR); 438 if (astlpc->kcs_fd < 0) 439 return -1; 440 441 return 0; 442 } 443 444 static int __mctp_astlpc_fileio_kcs_read(void *arg, 445 enum mctp_binding_astlpc_kcs_reg reg, uint8_t *val) 446 { 447 struct mctp_binding_astlpc *astlpc = arg; 448 off_t offset = reg; 449 int rc; 450 451 rc = pread(astlpc->kcs_fd, val, 1, offset); 452 453 return rc == 1 ? 0 : -1; 454 } 455 456 static int __mctp_astlpc_fileio_kcs_write(void *arg, 457 enum mctp_binding_astlpc_kcs_reg reg, uint8_t val) 458 { 459 struct mctp_binding_astlpc *astlpc = arg; 460 off_t offset = reg; 461 int rc; 462 463 rc = pwrite(astlpc->kcs_fd, &val, 1, offset); 464 465 return rc == 1 ? 0 : -1; 466 } 467 468 int mctp_astlpc_get_fd(struct mctp_binding_astlpc *astlpc) 469 { 470 return astlpc->kcs_fd; 471 } 472 473 struct mctp_binding_astlpc *mctp_astlpc_init_fileio(void) 474 { 475 struct mctp_binding_astlpc *astlpc; 476 int rc; 477 478 astlpc = __mctp_astlpc_init(); 479 if (!astlpc) 480 return NULL; 481 482 /* Set internal operations for kcs. We use direct accesses to the lpc 483 * map area */ 484 astlpc->ops.kcs_read = __mctp_astlpc_fileio_kcs_read; 485 astlpc->ops.kcs_write = __mctp_astlpc_fileio_kcs_write; 486 astlpc->ops_data = astlpc; 487 488 rc = mctp_astlpc_init_fileio_lpc(astlpc); 489 if (rc) { 490 free(astlpc); 491 return NULL; 492 } 493 494 rc = mctp_astlpc_init_fileio_kcs(astlpc); 495 if (rc) { 496 free(astlpc); 497 return NULL; 498 } 499 500 return astlpc; 501 } 502 #else 503 struct mctp_binding_astlpc * __attribute__((const)) 504 mctp_astlpc_init_fileio(void) 505 { 506 warnx("Missing support for file IO"); 507 return NULL; 508 } 509 510 int __attribute__((const)) mctp_astlpc_get_fd( 511 struct mctp_binding_astlpc *astlpc __attribute__((unused))) 512 { 513 warnx("Missing support for file IO"); 514 return -1; 515 } 516 #endif 517