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 while (true) { 105 tl_e = transfer_list_next(tl, tl_e); 106 if (!tl_e) 107 break; 108 109 DMSG("Entry %d:", i++); 110 DMSG("tag_id %#"PRIx16, tl_e->tag_id); 111 DMSG("hdr_size %#"PRIx8, tl_e->hdr_size); 112 DMSG("data_size %#"PRIx32, tl_e->data_size); 113 DMSG("data_addr %#"PRIxVA, 114 (vaddr_t)transfer_list_entry_data(tl_e)); 115 } 116 } 117 118 /******************************************************************************* 119 * Creating a transfer list in a specified reserved memory region. 120 * Compliant with 2.4.5 of Firmware Handoff specification (v0.9). 121 * @pa: Physical address for residing the new transfer list. 122 * @max_size: Maximum size of the new transfer list. 123 * Return pointer to the created transfer list or NULL on error. 124 ******************************************************************************/ 125 struct transfer_list_header *transfer_list_init(paddr_t pa, size_t max_size) 126 { 127 struct transfer_list_header *tl = NULL; 128 int align = TL_ALIGNMENT_FROM_ORDER(TRANSFER_LIST_INIT_MAX_ALIGN); 129 130 if (!pa || !max_size) 131 return NULL; 132 133 if (!IS_ALIGNED(pa, align) || !IS_ALIGNED(max_size, align) || 134 max_size < sizeof(*tl)) 135 return NULL; 136 137 tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, pa, max_size); 138 if (!tl) 139 return NULL; 140 141 memset(tl, 0, max_size); 142 tl->signature = TRANSFER_LIST_SIGNATURE; 143 tl->version = TRANSFER_LIST_VERSION; 144 tl->hdr_size = sizeof(*tl); 145 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; /* initial max align */ 146 tl->size = sizeof(*tl); /* initial size is the size of header */ 147 tl->max_size = max_size; 148 149 transfer_list_update_checksum(tl); 150 151 return tl; 152 } 153 154 /******************************************************************************* 155 * Relocating a transfer list to a specified reserved memory region. 156 * Compliant with 2.4.6 of Firmware Handoff specification (v0.9). 157 * @tl: Pointer to the transfer list for relocating. 158 * @pa: Physical address for relocating the transfer list. 159 * @max_size: Maximum size of the transfer list after relocating 160 * Return pointer to the relocated transfer list or NULL on error. 161 ******************************************************************************/ 162 struct transfer_list_header * 163 transfer_list_relocate(struct transfer_list_header *tl, paddr_t pa, 164 size_t max_size) 165 { 166 paddr_t new_addr = 0; 167 struct transfer_list_header *new_tl = NULL; 168 size_t new_max_size = 0; 169 170 if (!tl || !pa || !max_size) 171 return NULL; 172 173 new_addr = get_align_base_addr(pa, tl); 174 new_max_size = max_size - (new_addr - pa); 175 176 /* The new space is not sufficient for the TL */ 177 if (tl->size > new_max_size) 178 return NULL; 179 180 new_tl = core_mmu_add_mapping(MEM_AREA_TRANSFER_LIST, new_addr, 181 new_max_size); 182 if (!new_tl) 183 return NULL; 184 185 memmove(new_tl, tl, tl->size); 186 new_tl->max_size = new_max_size; 187 188 transfer_list_update_checksum(new_tl); 189 transfer_list_unmap_nosync(tl); 190 191 return new_tl; 192 } 193 194 /******************************************************************************* 195 * Verifying the header of a transfer list. 196 * Compliant with 2.4.1 of Firmware Handoff specification (v0.9). 197 * @tl: Pointer to the transfer list. 198 * Return transfer list operation status code. 199 ******************************************************************************/ 200 int transfer_list_check_header(const struct transfer_list_header *tl) 201 { 202 if (!tl) 203 return TL_OPS_NONE; 204 205 if (tl->signature != TRANSFER_LIST_SIGNATURE) { 206 EMSG("Bad transfer list signature %#"PRIx32, tl->signature); 207 return TL_OPS_NONE; 208 } 209 210 if (!tl->max_size) { 211 EMSG("Bad transfer list max size %#"PRIx32, tl->max_size); 212 return TL_OPS_NONE; 213 } 214 215 if (tl->size > tl->max_size) { 216 EMSG("Bad transfer list size %#"PRIx32, tl->size); 217 return TL_OPS_NONE; 218 } 219 220 if (tl->hdr_size != sizeof(struct transfer_list_header)) { 221 EMSG("Bad transfer list header size %#"PRIx8, tl->hdr_size); 222 return TL_OPS_NONE; 223 } 224 225 if (!transfer_list_verify_checksum(tl)) { 226 EMSG("Bad transfer list checksum %#"PRIx8, tl->checksum); 227 return TL_OPS_NONE; 228 } 229 230 if (tl->version == 0) { 231 EMSG("Transfer list version is invalid"); 232 return TL_OPS_NONE; 233 } else if (tl->version == TRANSFER_LIST_VERSION) { 234 DMSG("Transfer list version is valid for all operations"); 235 return TL_OPS_ALL; 236 } else if (tl->version > TRANSFER_LIST_VERSION) { 237 DMSG("Transfer list version is valid for read-only"); 238 return TL_OPS_RO; 239 } 240 241 DMSG("Old transfer list version is detected"); 242 return TL_OPS_CUS; 243 } 244 245 /******************************************************************************* 246 * Enumerate the next transfer entry. 247 * @tl: Pointer to the transfer list. 248 * @cur: Pointer to the current transfer entry where we want to search for the 249 * next one. 250 * Return pointer to the next transfer entry or NULL on error or if @cur is the 251 * last entry. 252 ******************************************************************************/ 253 struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl, 254 struct transfer_list_entry *cur) 255 { 256 struct transfer_list_entry *tl_e = NULL; 257 vaddr_t tl_ev = 0; 258 vaddr_t va = 0; 259 vaddr_t ev = 0; 260 size_t sz = 0; 261 262 if (!tl) 263 return NULL; 264 265 tl_ev = (vaddr_t)tl + tl->size; 266 267 if (cur) { 268 va = (vaddr_t)cur; 269 /* check if the total size overflow */ 270 if (ADD_OVERFLOW(cur->hdr_size, cur->data_size, &sz)) 271 return NULL; 272 /* roundup to the next entry */ 273 if (ADD_OVERFLOW(va, sz, &va) || 274 ROUNDUP_OVERFLOW(va, TRANSFER_LIST_GRANULE, &va)) 275 return NULL; 276 } else { 277 va = (vaddr_t)tl + tl->hdr_size; 278 } 279 280 tl_e = (struct transfer_list_entry *)va; 281 282 if (va + sizeof(*tl_e) > tl_ev || tl_e->hdr_size < sizeof(*tl_e) || 283 ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) || 284 ADD_OVERFLOW(va, sz, &ev) || ev > tl_ev) 285 return NULL; 286 287 return tl_e; 288 } 289 290 /******************************************************************************* 291 * Calculate the byte sum (modulo 256) of a transfer list. 292 * @tl: Pointer to the transfer list. 293 * Return byte sum of the transfer list. 294 ******************************************************************************/ 295 static uint8_t calc_byte_sum(const struct transfer_list_header *tl) 296 { 297 uint8_t *b = (uint8_t *)tl; 298 uint8_t cs = 0; 299 size_t n = 0; 300 301 if (!tl) 302 return 0; 303 304 for (n = 0; n < tl->size; n++) 305 cs += b[n]; 306 307 return cs; 308 } 309 310 /******************************************************************************* 311 * Update the checksum of a transfer list. 312 * @tl: Pointer to the transfer list. 313 * Return updated checksum of the transfer list. 314 ******************************************************************************/ 315 void transfer_list_update_checksum(struct transfer_list_header *tl) 316 { 317 uint8_t cs = 0; 318 319 if (!tl) 320 return; 321 322 cs = calc_byte_sum(tl); 323 cs -= tl->checksum; 324 cs = 256 - cs; 325 tl->checksum = cs; 326 assert(transfer_list_verify_checksum(tl)); 327 } 328 329 /******************************************************************************* 330 * Verify the checksum of a transfer list. 331 * @tl: Pointer to the transfer list. 332 * Return true if verified or false if not. 333 ******************************************************************************/ 334 bool transfer_list_verify_checksum(const struct transfer_list_header *tl) 335 { 336 return !calc_byte_sum(tl); 337 } 338 339 /******************************************************************************* 340 * Update the data size of a transfer entry. 341 * @tl: Pointer to the transfer list. 342 * @tl_e: Pointer to the transfer entry. 343 * @new_data_size: New data size of the transfer entry. 344 * Return true on success or false on error. 345 ******************************************************************************/ 346 bool transfer_list_set_data_size(struct transfer_list_header *tl, 347 struct transfer_list_entry *tl_e, 348 uint32_t new_data_size) 349 { 350 vaddr_t tl_old_ev = 0; 351 vaddr_t new_ev = 0; 352 vaddr_t old_ev = 0; 353 vaddr_t ru_new_ev = 0; 354 struct transfer_list_entry *dummy_te = NULL; 355 size_t gap = 0; 356 size_t mov_dis = 0; 357 size_t sz = 0; 358 359 if (!tl || !tl_e) 360 return false; 361 362 tl_old_ev = (vaddr_t)tl + tl->size; 363 364 /* 365 * Calculate the old and new end of transfer entry 366 * both must be roundup to align with TRANSFER_LIST_GRANULE 367 */ 368 if (ADD_OVERFLOW(tl_e->hdr_size, tl_e->data_size, &sz) || 369 ADD_OVERFLOW((vaddr_t)tl_e, sz, &old_ev) || 370 ROUNDUP_OVERFLOW(old_ev, TRANSFER_LIST_GRANULE, &old_ev)) 371 return false; 372 373 if (ADD_OVERFLOW(tl_e->hdr_size, new_data_size, &sz) || 374 ADD_OVERFLOW((vaddr_t)tl_e, sz, &new_ev) || 375 ROUNDUP_OVERFLOW(new_ev, TRANSFER_LIST_GRANULE, &new_ev)) 376 return false; 377 378 if (new_ev > old_ev) { 379 /* 380 * Move distance should be roundup to meet the requirement of 381 * transfer entry data max alignment. 382 * Ensure that the increased size doesn't exceed the max size 383 * of TL 384 */ 385 mov_dis = new_ev - old_ev; 386 if (ROUNDUP_OVERFLOW(mov_dis, 387 TL_ALIGNMENT_FROM_ORDER(tl->alignment), 388 &mov_dis) || 389 tl->size + mov_dis > tl->max_size) { 390 return false; 391 } 392 ru_new_ev = old_ev + mov_dis; 393 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev); 394 tl->size += mov_dis; 395 gap = ru_new_ev - new_ev; 396 } else { 397 gap = old_ev - new_ev; 398 } 399 400 if (gap >= sizeof(*dummy_te)) { 401 /* Create a dummy transfer entry to fill up the gap */ 402 dummy_te = (struct transfer_list_entry *)new_ev; 403 dummy_te->tag_id = TL_TAG_EMPTY; 404 dummy_te->reserved0 = 0; 405 dummy_te->hdr_size = sizeof(*dummy_te); 406 dummy_te->data_size = gap - sizeof(*dummy_te); 407 } 408 409 tl_e->data_size = new_data_size; 410 411 transfer_list_update_checksum(tl); 412 return true; 413 } 414 415 /******************************************************************************* 416 * Remove a specified transfer entry from a transfer list. 417 * @tl: Pointer to the transfer list. 418 * @tl_e: Pointer to the transfer entry. 419 * Return true on success or false on error. 420 ******************************************************************************/ 421 bool transfer_list_rem(struct transfer_list_header *tl, 422 struct transfer_list_entry *tl_e) 423 { 424 if (!tl || !tl_e || (vaddr_t)tl_e > (vaddr_t)tl + tl->size) 425 return false; 426 427 tl_e->tag_id = TL_TAG_EMPTY; 428 tl_e->reserved0 = 0; 429 transfer_list_update_checksum(tl); 430 return true; 431 } 432 433 /******************************************************************************* 434 * Add a new transfer entry into a transfer list. 435 * Compliant with 2.4.3 of Firmware Handoff specification (v0.9). 436 * @tl: Pointer to the transfer list. 437 * @tag_id: Tag ID for the new transfer entry. 438 * @data_size: Data size of the new transfer entry. 439 * @data: Pointer to the data for the new transfer entry. 440 * NULL to skip data copying. 441 * Return pointer to the added transfer entry or NULL on error. 442 ******************************************************************************/ 443 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl, 444 uint16_t tag_id, 445 uint32_t data_size, 446 const void *data) 447 { 448 vaddr_t max_tl_ev = 0; 449 vaddr_t tl_ev = 0; 450 vaddr_t ev = 0; 451 struct transfer_list_entry *tl_e = NULL; 452 size_t sz = 0; 453 454 if (!tl) 455 return NULL; 456 457 max_tl_ev = (vaddr_t)tl + tl->max_size; 458 tl_ev = (vaddr_t)tl + tl->size; 459 ev = tl_ev; 460 461 /* 462 * Skip the step 1 (optional step). 463 * New transfer entry will be added into the tail 464 */ 465 if (ADD_OVERFLOW(sizeof(*tl_e), data_size, &sz) || 466 ADD_OVERFLOW(ev, sz, &ev) || 467 ROUNDUP_OVERFLOW(ev, TRANSFER_LIST_GRANULE, &ev) || 468 ev > max_tl_ev) { 469 return NULL; 470 } 471 472 tl_e = (struct transfer_list_entry *)tl_ev; 473 *tl_e = (struct transfer_list_entry){ 474 .tag_id = tag_id, 475 .hdr_size = sizeof(*tl_e), 476 .data_size = data_size, 477 }; 478 479 tl->size += ev - tl_ev; 480 481 if (data) 482 memmove(tl_e + tl_e->hdr_size, data, data_size); 483 484 transfer_list_update_checksum(tl); 485 486 return tl_e; 487 } 488 489 /******************************************************************************* 490 * Add a new transfer entry into a transfer list with specified new data 491 * alignment requirement. 492 * Compliant with 2.4.4 of Firmware Handoff specification (v0.9). 493 * @tl: Pointer to the transfer list. 494 * @tag_id: Tag ID for the new transfer entry. 495 * @data_size: Data size of the new transfer entry. 496 * @data: Pointer to the data for the new transfer entry. 497 * @alignment: New data alignment specified as a power of two. 498 * Return pointer to the added transfer entry or NULL on error. 499 ******************************************************************************/ 500 struct transfer_list_entry * 501 transfer_list_add_with_align(struct transfer_list_header *tl, uint16_t tag_id, 502 uint32_t data_size, const void *data, 503 uint8_t alignment) 504 { 505 struct transfer_list_entry *tl_e = NULL; 506 vaddr_t tl_ev = 0; 507 vaddr_t ev = 0; 508 vaddr_t new_tl_ev = 0; 509 size_t dummy_te_data_sz = 0; 510 511 if (!tl) 512 return NULL; 513 514 tl_ev = (vaddr_t)tl + tl->size; 515 ev = tl_ev + sizeof(struct transfer_list_entry); 516 517 if (!IS_ALIGNED(ev, TL_ALIGNMENT_FROM_ORDER(alignment))) { 518 /* 519 * Transfer entry data address is not aligned to the new 520 * alignment. Fill the gap with an empty transfer entry as a 521 * placeholder before adding the desired transfer entry 522 */ 523 new_tl_ev = ROUNDUP(ev, TL_ALIGNMENT_FROM_ORDER(alignment)) - 524 sizeof(struct transfer_list_entry); 525 assert(new_tl_ev - tl_ev > sizeof(struct transfer_list_entry)); 526 dummy_te_data_sz = new_tl_ev - tl_ev - 527 sizeof(struct transfer_list_entry); 528 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz, 529 NULL)) { 530 return NULL; 531 } 532 } 533 534 tl_e = transfer_list_add(tl, tag_id, data_size, data); 535 536 if (alignment > tl->alignment) { 537 tl->alignment = alignment; 538 transfer_list_update_checksum(tl); 539 } 540 541 return tl_e; 542 } 543 544 /******************************************************************************* 545 * Search for an existing transfer entry with the specified tag id from a 546 * transfer list. 547 * @tl: Pointer to the transfer list. 548 * @tag_id: Tag ID to match a transfer entry. 549 * Return pointer to the found transfer entry or NULL if not found. 550 ******************************************************************************/ 551 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl, 552 uint16_t tag_id) 553 { 554 struct transfer_list_entry *tl_e = NULL; 555 556 do { 557 tl_e = transfer_list_next(tl, tl_e); 558 } while (tl_e && tl_e->tag_id != tag_id); 559 560 return tl_e; 561 } 562 563 /******************************************************************************* 564 * Retrieve the data pointer of a specified transfer entry. 565 * @tl_e: Pointer to the transfer entry. 566 * Return pointer to the transfer entry data or NULL on error. 567 ******************************************************************************/ 568 void *transfer_list_entry_data(struct transfer_list_entry *tl_e) 569 { 570 if (!tl_e) 571 return NULL; 572 573 return (uint8_t *)tl_e + tl_e->hdr_size; 574 } 575