1 // SPDX-License-Identifier: BSD-2-Clause 2 /* 3 * Copyright (c) 2023, Linaro Limited 4 */ 5 6 /******************************************************************************* 7 * Transfer list library compliant with the Firmware Handoff specification at: 8 * https://github.com/FirmwareHandoff/firmware_handoff 9 ******************************************************************************/ 10 11 #include <kernel/cache_helpers.h> 12 #include <kernel/panic.h> 13 #include <kernel/transfer_list.h> 14 #include <mm/core_memprot.h> 15 #include <mm/core_mmu.h> 16 #include <string.h> 17 #include <util.h> 18 19 /******************************************************************************* 20 * Adapt a physical address to match the maximum transfer entry data alignment 21 * required by an existing transfer list. 22 * Compliant with 2.4.6 of Firmware Handoff specification (v0.9). 23 * @pa: Physical address for adapting. 24 * @tl: Pointer to the existing transfer list. 25 * Return the adapted physical address. 26 ******************************************************************************/ 27 static paddr_t get_align_base_addr(paddr_t pa, 28 struct transfer_list_header *tl) 29 { 30 paddr_t align_mask = TL_ALIGNMENT_FROM_ORDER(tl->alignment) - 1; 31 paddr_t align_off = (paddr_t)tl & align_mask; 32 paddr_t new_addr = (pa & ~align_mask) + align_off; 33 34 if (new_addr < pa) 35 new_addr += TL_ALIGNMENT_FROM_ORDER(tl->alignment); 36 37 return new_addr; 38 } 39 40 static void unmap_list(struct transfer_list_header *tl, size_t sz) 41 { 42 if (core_mmu_remove_mapping(MEM_AREA_TRANSFER_LIST, tl, sz)) 43 panic("Failed to remove transfer list mapping"); 44 } 45 46 struct transfer_list_header *transfer_list_map(paddr_t pa) 47 { 48 struct transfer_list_header *tl = NULL; 49 size_t sz = SMALL_PAGE_SIZE; 50 size_t old_sz = 0; 51 52 while (true) { 53 tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, sz); 54 if (!tl) { 55 EMSG("Failed to map TL with PA %#"PRIxPA", size %#zx", 56 pa, sz); 57 return NULL; 58 } 59 old_sz = sz; 60 61 if (transfer_list_check_header(tl) == TL_OPS_NONE) { 62 unmap_list(tl, sz); 63 return NULL; 64 } 65 66 if (tl->max_size <= sz) 67 return tl; 68 69 sz = ROUNDUP(tl->max_size, SMALL_PAGE_SIZE); 70 unmap_list(tl, old_sz); 71 } 72 } 73 74 void transfer_list_unmap_sync(struct transfer_list_header *tl) 75 { 76 size_t sz = tl->max_size; 77 78 transfer_list_update_checksum(tl); 79 dcache_cleaninv_range(tl, sz); 80 unmap_list(tl, sz); 81 } 82 83 void transfer_list_unmap_nosync(struct transfer_list_header *tl) 84 { 85 unmap_list(tl, tl->max_size); 86 } 87 88 void transfer_list_dump(struct transfer_list_header *tl) 89 { 90 struct transfer_list_entry *tl_e = NULL; 91 int i __maybe_unused = 0; 92 93 if (!tl) 94 return; 95 96 DMSG("Dump transfer list:"); 97 DMSG("signature %#"PRIx32, tl->signature); 98 DMSG("checksum %#"PRIx8, tl->checksum); 99 DMSG("version %#"PRIx8, tl->version); 100 DMSG("hdr_size %#"PRIx8, tl->hdr_size); 101 DMSG("alignment %#"PRIx8, tl->alignment); 102 DMSG("size %#"PRIx32, tl->size); 103 DMSG("max_size %#"PRIx32, tl->max_size); 104 DMSG("flags %#"PRIx32, tl->flags); 105 while (true) { 106 tl_e = transfer_list_next(tl, tl_e); 107 if (!tl_e) 108 break; 109 110 DMSG("Entry %d:", i++); 111 DMSG("tag_id %#"PRIx16, tl_e->tag_id); 112 DMSG("hdr_size %#"PRIx8, tl_e->hdr_size); 113 DMSG("data_size %#"PRIx32, tl_e->data_size); 114 DMSG("data_addr %#"PRIxVA, 115 (vaddr_t)transfer_list_entry_data(tl_e)); 116 } 117 } 118 119 /******************************************************************************* 120 * Creating a transfer list in a specified reserved memory region. 121 * Compliant with 2.4.5 of Firmware Handoff specification (v0.9). 122 * @pa: Physical address for residing the new transfer list. 123 * @max_size: Maximum size of the new transfer list. 124 * Return pointer to the created transfer list or NULL on error. 125 ******************************************************************************/ 126 struct transfer_list_header *transfer_list_init(paddr_t pa, size_t max_size) 127 { 128 struct transfer_list_header *tl = NULL; 129 int align = TL_ALIGNMENT_FROM_ORDER(TRANSFER_LIST_INIT_MAX_ALIGN); 130 131 if (!pa || !max_size) 132 return NULL; 133 134 if (!IS_ALIGNED(pa, align) || !IS_ALIGNED(max_size, align) || 135 max_size < sizeof(*tl)) 136 return NULL; 137 138 tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, max_size); 139 if (!tl) 140 return NULL; 141 142 memset(tl, 0, max_size); 143 tl->signature = TRANSFER_LIST_SIGNATURE; 144 tl->version = TRANSFER_LIST_VERSION; 145 tl->hdr_size = sizeof(*tl); 146 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */ 147 tl->size = sizeof(*tl); /* initial size is the size of header */ 148 tl->max_size = max_size; 149 tl->flags = TL_FLAGS_HAS_CHECKSUM; 150 151 transfer_list_update_checksum(tl); 152 153 return tl; 154 } 155 156 /******************************************************************************* 157 * Relocating a transfer list to a specified reserved memory region. 158 * Compliant with 2.4.6 of Firmware Handoff specification (v0.9). 159 * @tl: Pointer to the transfer list for relocating. 160 * @pa: Physical address for relocating the transfer list. 161 * @max_size: Maximum size of the transfer list after relocating 162 * Return pointer to the relocated transfer list or NULL on error. 163 ******************************************************************************/ 164 struct transfer_list_header * 165 transfer_list_relocate(struct transfer_list_header *tl, paddr_t pa, 166 size_t max_size) 167 { 168 paddr_t new_addr = 0; 169 struct transfer_list_header *new_tl = NULL; 170 size_t new_max_size = 0; 171 172 if (!tl || !pa || !max_size) 173 return NULL; 174 175 new_addr = get_align_base_addr(pa, tl); 176 new_max_size = max_size - (new_addr - pa); 177 178 /* The new space is not sufficient for the TL */ 179 if (tl->size > new_max_size) 180 return NULL; 181 182 new_tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, new_addr, 183 new_max_size); 184 if (!new_tl) 185 return NULL; 186 187 memmove(new_tl, tl, tl->size); 188 new_tl->max_size = new_max_size; 189 190 transfer_list_update_checksum(new_tl); 191 transfer_list_unmap_nosync(tl); 192 193 return new_tl; 194 } 195 196 /******************************************************************************* 197 * Verifying the header of a transfer list. 198 * Compliant with 2.4.1 of Firmware Handoff specification (v0.9). 199 * @tl: Pointer to the transfer list. 200 * Return transfer list operation status code. 201 ******************************************************************************/ 202 int transfer_list_check_header(const struct transfer_list_header *tl) 203 { 204 if (!tl) 205 return TL_OPS_NONE; 206 207 if (tl->signature != TRANSFER_LIST_SIGNATURE) { 208 EMSG("Bad transfer list signature %#"PRIx32, tl->signature); 209 return TL_OPS_NONE; 210 } 211 212 if (!tl->max_size) { 213 EMSG("Bad transfer list max size %#"PRIx32, tl->max_size); 214 return TL_OPS_NONE; 215 } 216 217 if (tl->size > tl->max_size) { 218 EMSG("Bad transfer list size %#"PRIx32, tl->size); 219 return TL_OPS_NONE; 220 } 221 222 if (tl->hdr_size != sizeof(struct transfer_list_header)) { 223 EMSG("Bad transfer list header size %#"PRIx8, tl->hdr_size); 224 return TL_OPS_NONE; 225 } 226 227 if (!transfer_list_verify_checksum(tl)) { 228 EMSG("Bad transfer list checksum %#"PRIx8, tl->checksum); 229 return TL_OPS_NONE; 230 } 231 232 if (tl->version == 0) { 233 EMSG("Transfer list version is invalid"); 234 return TL_OPS_NONE; 235 } else if (tl->version == TRANSFER_LIST_VERSION) { 236 DMSG("Transfer list version is valid for all operations"); 237 return TL_OPS_ALL; 238 } else if (tl->version > TRANSFER_LIST_VERSION) { 239 DMSG("Transfer list version is valid for read-only"); 240 return TL_OPS_RO; 241 } 242 243 DMSG("Old transfer list version is detected"); 244 return TL_OPS_CUS; 245 } 246 247 /******************************************************************************* 248 * Enumerate the next transfer entry. 249 * @tl: Pointer to the transfer list. 250 * @cur: Pointer to the current transfer entry where we want to search for the 251 * next one. 252 * Return pointer to the next transfer entry or NULL on error or if @cur is the 253 * last entry. 254 ******************************************************************************/ 255 struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl, 256 struct transfer_list_entry *cur) 257 { 258 struct transfer_list_entry *tl_e = NULL; 259 vaddr_t tl_ev = 0; 260 vaddr_t va = 0; 261 vaddr_t ev = 0; 262 size_t sz = 0; 263 264 if (!tl) 265 return NULL; 266 267 tl_ev = (vaddr_t)tl + tl->size; 268 269 if (cur) { 270 va = (vaddr_t)cur; 271 /* check if the total size overflow */ 272 if (ADD_OVERFLOW(cur->hdr_size, cur->data_size, &sz)) 273 return NULL; 274 /* roundup to the next entry */ 275 if (ADD_OVERFLOW(va, sz, &va) || 276 ROUNDUP_OVERFLOW(va, TRANSFER_LIST_GRANULE, &va)) 277 return NULL; 278 } else { 279 va = (vaddr_t)tl + tl->hdr_size; 280 } 281 282 tl_e = (struct transfer_list_entry *)va; 283 284 if (va + sizeof(*tl_e) > tl_ev || tl_e->hdr_size < sizeof(*tl_e) || 285 ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) || 286 ADD_OVERFLOW(va, sz, &ev) || ev > tl_ev) 287 return NULL; 288 289 return tl_e; 290 } 291 292 /******************************************************************************* 293 * Calculate the byte sum (modulo 256) of a transfer list. 294 * @tl: Pointer to the transfer list. 295 * Return byte sum of the transfer list. 296 ******************************************************************************/ 297 static uint8_t calc_byte_sum(const struct transfer_list_header *tl) 298 { 299 uint8_t *b = (uint8_t *)tl; 300 uint8_t cs = 0; 301 size_t n = 0; 302 303 for (n = 0; n < tl->size; n++) 304 cs += b[n]; 305 306 return cs; 307 } 308 309 /******************************************************************************* 310 * Update the checksum of a transfer list. 311 * @tl: Pointer to the transfer list. 312 * Return updated checksum of the transfer list. 313 ******************************************************************************/ 314 void transfer_list_update_checksum(struct transfer_list_header *tl) 315 { 316 uint8_t cs = 0; 317 318 if (!tl || !(tl->flags & TL_FLAGS_HAS_CHECKSUM)) 319 return; 320 321 cs = calc_byte_sum(tl); 322 cs -= tl->checksum; 323 cs = 256 - cs; 324 tl->checksum = cs; 325 assert(transfer_list_verify_checksum(tl)); 326 } 327 328 /******************************************************************************* 329 * Verify the checksum of a transfer list. 330 * @tl: Pointer to the transfer list. 331 * Return true if verified or false if not. 332 ******************************************************************************/ 333 bool transfer_list_verify_checksum(const struct transfer_list_header *tl) 334 { 335 if (!tl) 336 return false; 337 338 if (!(tl->flags & TL_FLAGS_HAS_CHECKSUM)) 339 return true; 340 341 return !calc_byte_sum(tl); 342 } 343 344 /******************************************************************************* 345 * Update the data size of a transfer entry. 346 * @tl: Pointer to the transfer list. 347 * @tl_e: Pointer to the transfer entry. 348 * @new_data_size: New data size of the transfer entry. 349 * Return true on success or false on error. 350 ******************************************************************************/ 351 bool transfer_list_set_data_size(struct transfer_list_header *tl, 352 struct transfer_list_entry *tl_e, 353 uint32_t new_data_size) 354 { 355 vaddr_t tl_old_ev = 0; 356 vaddr_t new_ev = 0; 357 vaddr_t old_ev = 0; 358 vaddr_t r_new_ev = 0; 359 struct transfer_list_entry *dummy_te = NULL; 360 size_t gap = 0; 361 size_t mov_dis = 0; 362 size_t sz = 0; 363 364 if (!tl || !tl_e) 365 return false; 366 367 tl_old_ev = (vaddr_t)tl + tl->size; 368 369 /* 370 * Calculate the old and new end of transfer entry 371 * both must be roundup to align with TRANSFER_LIST_GRANULE 372 */ 373 if (ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) || 374 ADD_OVERFLOW((vaddr_t)tl_e, sz, &old_ev) || 375 ROUNDUP_OVERFLOW(old_ev, TRANSFER_LIST_GRANULE, &old_ev)) 376 return false; 377 378 if (ADD_OVERFLOW(tl_e->hdr_size, new_data_size, &sz) || 379 ADD_OVERFLOW((vaddr_t)tl_e, sz, &new_ev) || 380 ROUNDUP_OVERFLOW(new_ev, TRANSFER_LIST_GRANULE, &new_ev)) 381 return false; 382 383 if (new_ev > old_ev) { 384 /* 385 * Move distance should be rounded up to match the entry data 386 * alignment. 387 * Ensure that the increased size doesn't exceed the max size 388 * of TL 389 */ 390 mov_dis = new_ev - old_ev; 391 if (ROUNDUP_OVERFLOW(mov_dis, 392 TL_ALIGNMENT_FROM_ORDER(tl->alignment), 393 &mov_dis) || 394 tl->size + mov_dis > tl->max_size) { 395 return false; 396 } 397 r_new_ev = old_ev + mov_dis; 398 tl->size += mov_dis; 399 } else { 400 /* 401 * Move distance should be rounded down to match the entry data 402 * alignment. 403 */ 404 mov_dis = ROUNDDOWN(old_ev - new_ev, 405 TL_ALIGNMENT_FROM_ORDER(tl->alignment)); 406 r_new_ev = old_ev - mov_dis; 407 tl->size -= mov_dis; 408 } 409 /* Move all following entries to fit in the expanded or shrunk space */ 410 memmove((void *)r_new_ev, (void *)old_ev, tl_old_ev - old_ev); 411 412 /* 413 * Fill the gap due to round up/down with a void entry if the size of 414 * the gap is more than an entry header. 415 */ 416 gap = r_new_ev - new_ev; 417 if (gap >= sizeof(*dummy_te)) { 418 /* Create a dummy transfer entry to fill up the gap */ 419 dummy_te = (struct transfer_list_entry *)new_ev; 420 dummy_te->tag_id = TL_TAG_EMPTY; 421 dummy_te->reserved0 = 0; 422 dummy_te->hdr_size = sizeof(*dummy_te); 423 dummy_te->data_size = gap - sizeof(*dummy_te); 424 } 425 426 tl_e->data_size = new_data_size; 427 428 transfer_list_update_checksum(tl); 429 return true; 430 } 431 432 /******************************************************************************* 433 * Remove a specified transfer entry from a transfer list. 434 * @tl: Pointer to the transfer list. 435 * @tl_e: Pointer to the transfer entry. 436 * Return true on success or false on error. 437 ******************************************************************************/ 438 bool transfer_list_rem(struct transfer_list_header *tl, 439 struct transfer_list_entry *tl_e) 440 { 441 if (!tl || !tl_e || (vaddr_t)tl_e > (vaddr_t)tl + tl->size) 442 return false; 443 444 tl_e->tag_id = TL_TAG_EMPTY; 445 tl_e->reserved0 = 0; 446 transfer_list_update_checksum(tl); 447 return true; 448 } 449 450 /******************************************************************************* 451 * Add a new transfer entry into a transfer list. 452 * Compliant with 2.4.3 of Firmware Handoff specification (v0.9). 453 * @tl: Pointer to the transfer list. 454 * @tag_id: Tag ID for the new transfer entry. 455 * @data_size: Data size of the new transfer entry. 456 * @data: Pointer to the data for the new transfer entry. 457 * NULL to skip data copying. 458 * Return pointer to the added transfer entry or NULL on error. 459 ******************************************************************************/ 460 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl, 461 uint16_t tag_id, 462 uint32_t data_size, 463 const void *data) 464 { 465 vaddr_t max_tl_ev = 0; 466 vaddr_t tl_ev = 0; 467 vaddr_t ev = 0; 468 struct transfer_list_entry *tl_e = NULL; 469 size_t sz = 0; 470 471 if (!tl) 472 return NULL; 473 474 max_tl_ev = (vaddr_t)tl + tl->max_size; 475 tl_ev = (vaddr_t)tl + tl->size; 476 ev = tl_ev; 477 478 /* 479 * Skip the step 1 (optional step). 480 * New transfer entry will be added into the tail 481 */ 482 if (ADD_OVERFLOW(sizeof(*tl_e), data_size, &sz) || 483 ADD_OVERFLOW(ev, sz, &ev) || 484 ROUNDUP_OVERFLOW(ev, TRANSFER_LIST_GRANULE, &ev) || 485 ev > max_tl_ev) { 486 return NULL; 487 } 488 489 tl_e = (struct transfer_list_entry *)tl_ev; 490 *tl_e = (struct transfer_list_entry){ 491 .tag_id = tag_id, 492 .hdr_size = sizeof(*tl_e), 493 .data_size = data_size, 494 }; 495 496 tl->size += ev - tl_ev; 497 498 if (data) 499 memmove(tl_e + tl_e->hdr_size, data, data_size); 500 501 transfer_list_update_checksum(tl); 502 503 return tl_e; 504 } 505 506 /******************************************************************************* 507 * Add a new transfer entry into a transfer list with specified new data 508 * alignment requirement. 509 * Compliant with 2.4.4 of Firmware Handoff specification (v0.9). 510 * @tl: Pointer to the transfer list. 511 * @tag_id: Tag ID for the new transfer entry. 512 * @data_size: Data size of the new transfer entry. 513 * @data: Pointer to the data for the new transfer entry. 514 * @alignment: New data alignment specified as a power of two. 515 * Return pointer to the added transfer entry or NULL on error. 516 ******************************************************************************/ 517 struct transfer_list_entry * 518 transfer_list_add_with_align(struct transfer_list_header *tl, uint16_t tag_id, 519 uint32_t data_size, const void *data, 520 uint8_t alignment) 521 { 522 struct transfer_list_entry *tl_e = NULL; 523 vaddr_t tl_ev = 0; 524 vaddr_t ev = 0; 525 vaddr_t new_tl_ev = 0; 526 size_t dummy_te_data_sz = 0; 527 528 if (!tl) 529 return NULL; 530 531 tl_ev = (vaddr_t)tl + tl->size; 532 ev = tl_ev + sizeof(struct transfer_list_entry); 533 534 if (!IS_ALIGNED(ev, TL_ALIGNMENT_FROM_ORDER(alignment))) { 535 /* 536 * Transfer entry data address is not aligned to the new 537 * alignment. Fill the gap with an empty transfer entry as a 538 * placeholder before adding the desired transfer entry 539 */ 540 new_tl_ev = ROUNDUP(ev, TL_ALIGNMENT_FROM_ORDER(alignment)) - 541 sizeof(struct transfer_list_entry); 542 assert(new_tl_ev - tl_ev > sizeof(struct transfer_list_entry)); 543 dummy_te_data_sz = new_tl_ev - tl_ev - 544 sizeof(struct transfer_list_entry); 545 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz, 546 NULL)) { 547 return NULL; 548 } 549 } 550 551 tl_e = transfer_list_add(tl, tag_id, data_size, data); 552 553 if (alignment > tl->alignment) { 554 tl->alignment = alignment; 555 transfer_list_update_checksum(tl); 556 } 557 558 return tl_e; 559 } 560 561 /******************************************************************************* 562 * Search for an existing transfer entry with the specified tag id from a 563 * transfer list. 564 * @tl: Pointer to the transfer list. 565 * @tag_id: Tag ID to match a transfer entry. 566 * Return pointer to the found transfer entry or NULL if not found. 567 ******************************************************************************/ 568 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl, 569 uint16_t tag_id) 570 { 571 struct transfer_list_entry *tl_e = NULL; 572 573 do { 574 tl_e = transfer_list_next(tl, tl_e); 575 } while (tl_e && tl_e->tag_id != tag_id); 576 577 return tl_e; 578 } 579 580 /******************************************************************************* 581 * Retrieve the data pointer of a specified transfer entry. 582 * @tl_e: Pointer to the transfer entry. 583 * Return pointer to the transfer entry data or NULL on error. 584 ******************************************************************************/ 585 void *transfer_list_entry_data(struct transfer_list_entry *tl_e) 586 { 587 if (!tl_e) 588 return NULL; 589 590 return (uint8_t *)tl_e + tl_e->hdr_size; 591 } 592