1 /* 2 * Copyright (c) 2023, Linaro Limited and Contributors. All rights reserved. 3 * 4 * SPDX-License-Identifier: BSD-3-Clause 5 */ 6 7 #include <assert.h> 8 #include <inttypes.h> 9 #include <string.h> 10 11 #include <common/debug.h> 12 #include <lib/transfer_list.h> 13 #include <lib/utils_def.h> 14 15 void transfer_list_dump(struct transfer_list_header *tl) 16 { 17 struct transfer_list_entry *te = NULL; 18 int i = 0; 19 20 if (!tl) { 21 return; 22 } 23 NOTICE("Dump transfer list:\n"); 24 NOTICE("signature 0x%x\n", tl->signature); 25 NOTICE("checksum 0x%x\n", tl->checksum); 26 NOTICE("version 0x%x\n", tl->version); 27 NOTICE("hdr_size 0x%x\n", tl->hdr_size); 28 NOTICE("alignment 0x%x\n", tl->alignment); 29 NOTICE("size 0x%x\n", tl->size); 30 NOTICE("max_size 0x%x\n", tl->max_size); 31 while (true) { 32 te = transfer_list_next(tl, te); 33 if (!te) { 34 break; 35 } 36 NOTICE("Entry %d:\n", i++); 37 NOTICE("tag_id 0x%x\n", te->tag_id); 38 NOTICE("hdr_size 0x%x\n", te->hdr_size); 39 NOTICE("data_size 0x%x\n", te->data_size); 40 NOTICE("data_addr 0x%lx\n", 41 (unsigned long)transfer_list_entry_data(te)); 42 } 43 } 44 45 /******************************************************************************* 46 * Creating a transfer list in a reserved memory region specified 47 * Compliant to 2.4.5 of Firmware handoff specification (v0.9) 48 * Return pointer to the created transfer list or NULL on error 49 ******************************************************************************/ 50 struct transfer_list_header *transfer_list_init(void *addr, size_t max_size) 51 { 52 struct transfer_list_header *tl = addr; 53 54 if (!addr || max_size == 0) { 55 return NULL; 56 } 57 58 if (!is_aligned((uintptr_t)addr, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) || 59 !is_aligned(max_size, 1 << TRANSFER_LIST_INIT_MAX_ALIGN) || 60 max_size < sizeof(*tl)) { 61 return NULL; 62 } 63 64 memset(tl, 0, max_size); 65 tl->signature = TRANSFER_LIST_SIGNATURE; 66 tl->version = TRANSFER_LIST_VERSION; 67 tl->hdr_size = sizeof(*tl); 68 tl->alignment = TRANSFER_LIST_INIT_MAX_ALIGN; // initial max align 69 tl->size = sizeof(*tl); // initial size is the size of header 70 tl->max_size = max_size; 71 72 transfer_list_update_checksum(tl); 73 74 return tl; 75 } 76 77 /******************************************************************************* 78 * Relocating a transfer list to a reserved memory region specified 79 * Compliant to 2.4.6 of Firmware handoff specification (v0.9) 80 * Return true on success or false on error 81 ******************************************************************************/ 82 struct transfer_list_header *transfer_list_relocate( 83 struct transfer_list_header *tl, 84 void *addr, size_t max_size) 85 { 86 uintptr_t new_addr, align_mask, align_off; 87 struct transfer_list_header *new_tl; 88 uint32_t new_max_size; 89 90 if (!tl || !addr || max_size == 0) { 91 return NULL; 92 } 93 94 align_mask = (1 << tl->alignment) - 1; 95 align_off = (uintptr_t)tl & align_mask; 96 new_addr = ((uintptr_t)addr & ~align_mask) + align_off; 97 98 if (new_addr < (uintptr_t)addr) { 99 new_addr += (1 << tl->alignment); 100 } 101 102 new_max_size = max_size - (new_addr - (uintptr_t)addr); 103 104 // the new space is not sufficient for the tl 105 if (tl->size > new_max_size) { 106 return NULL; 107 } 108 109 new_tl = (struct transfer_list_header *)new_addr; 110 memmove(new_tl, tl, tl->size); 111 new_tl->max_size = new_max_size; 112 113 transfer_list_update_checksum(new_tl); 114 115 return new_tl; 116 } 117 118 /******************************************************************************* 119 * Verifying the header of a transfer list 120 * Compliant to 2.4.1 of Firmware handoff specification (v0.9) 121 * Return transfer list operation status code 122 ******************************************************************************/ 123 enum transfer_list_ops transfer_list_check_header( 124 const struct transfer_list_header *tl) 125 { 126 if (!tl) { 127 return TL_OPS_NON; 128 } 129 130 if (tl->signature != TRANSFER_LIST_SIGNATURE) { 131 ERROR("Bad transfer list signature %#"PRIx32"\n", 132 tl->signature); 133 return TL_OPS_NON; 134 } 135 136 if (!tl->max_size) { 137 ERROR("Bad transfer list max size %#"PRIx32"\n", 138 tl->max_size); 139 return TL_OPS_NON; 140 } 141 142 if (tl->size > tl->max_size) { 143 ERROR("Bad transfer list size %#"PRIx32"\n", tl->size); 144 return TL_OPS_NON; 145 } 146 147 if (tl->hdr_size != sizeof(struct transfer_list_header)) { 148 ERROR("Bad transfer list header size %#"PRIx32"\n", tl->hdr_size); 149 return TL_OPS_NON; 150 } 151 152 if (!transfer_list_verify_checksum(tl)) { 153 ERROR("Bad transfer list checksum %#"PRIx32"\n", tl->checksum); 154 return TL_OPS_NON; 155 } 156 157 if (tl->version == 0) { 158 ERROR("Transfer list version is invalid\n"); 159 return TL_OPS_NON; 160 } else if (tl->version == TRANSFER_LIST_VERSION) { 161 INFO("Transfer list version is valid for all operations\n"); 162 return TL_OPS_ALL; 163 } else if (tl->version > TRANSFER_LIST_VERSION) { 164 INFO("Transfer list version is valid for read-only\n"); 165 return TL_OPS_RO; 166 } 167 168 INFO("Old transfer list version is detected\n"); 169 return TL_OPS_CUS; 170 } 171 172 /******************************************************************************* 173 * Enumerate the next transfer entry 174 * Return pointer to the next transfer entry or NULL on error 175 ******************************************************************************/ 176 struct transfer_list_entry *transfer_list_next(struct transfer_list_header *tl, 177 struct transfer_list_entry *last) 178 { 179 struct transfer_list_entry *te = NULL; 180 uintptr_t tl_ev = 0; 181 uintptr_t va = 0; 182 uintptr_t ev = 0; 183 size_t sz = 0; 184 185 if (!tl) { 186 return NULL; 187 } 188 189 tl_ev = (uintptr_t)tl + tl->size; 190 191 if (last) { 192 va = (uintptr_t)last; 193 // check if the total size overflow 194 if (add_overflow(last->hdr_size, 195 last->data_size, &sz)) { 196 return NULL; 197 } 198 // roundup to the next entry 199 if (add_with_round_up_overflow(va, sz, 200 TRANSFER_LIST_GRANULE, &va)) { 201 return NULL; 202 } 203 } else { 204 va = (uintptr_t)tl + tl->hdr_size; 205 } 206 207 te = (struct transfer_list_entry *)va; 208 209 if (va + sizeof(*te) > tl_ev || te->hdr_size < sizeof(*te) || 210 add_overflow(te->hdr_size, te->data_size, &sz) || 211 add_overflow(va, sz, &ev) || 212 ev > tl_ev) { 213 return NULL; 214 } 215 216 return te; 217 } 218 219 /******************************************************************************* 220 * Calculate the byte sum of a transfer list 221 * Return byte sum of the transfer list 222 ******************************************************************************/ 223 static uint8_t calc_byte_sum(const struct transfer_list_header *tl) 224 { 225 uint8_t *b = (uint8_t *)tl; 226 uint8_t cs = 0; 227 size_t n = 0; 228 229 if (!tl) { 230 return 0; 231 } 232 233 for (n = 0; n < tl->size; n++) { 234 cs += b[n]; 235 } 236 237 return cs; 238 } 239 240 /******************************************************************************* 241 * Update the checksum of a transfer list 242 * Return updated checksum of the transfer list 243 ******************************************************************************/ 244 void transfer_list_update_checksum(struct transfer_list_header *tl) 245 { 246 uint8_t cs; 247 248 if (!tl) { 249 return; 250 } 251 252 cs = calc_byte_sum(tl); 253 cs -= tl->checksum; 254 cs = 256 - cs; 255 tl->checksum = cs; 256 assert(transfer_list_verify_checksum(tl)); 257 } 258 259 /******************************************************************************* 260 * Verify the checksum of a transfer list 261 * Return true if verified or false if not 262 ******************************************************************************/ 263 bool transfer_list_verify_checksum(const struct transfer_list_header *tl) 264 { 265 return !calc_byte_sum(tl); 266 } 267 268 /******************************************************************************* 269 * Update the data size of a transfer entry 270 * Return true on success or false on error 271 ******************************************************************************/ 272 bool transfer_list_set_data_size(struct transfer_list_header *tl, 273 struct transfer_list_entry *te, 274 uint32_t new_data_size) 275 { 276 uintptr_t tl_old_ev, new_ev = 0, old_ev = 0, ru_new_ev; 277 struct transfer_list_entry *dummy_te = NULL; 278 size_t gap = 0; 279 size_t mov_dis = 0; 280 size_t sz = 0; 281 282 if (!tl || !te) { 283 return false; 284 } 285 tl_old_ev = (uintptr_t)tl + tl->size; 286 287 // calculate the old and new end of TE 288 // both must be roundup to align with TRANSFER_LIST_GRANULE 289 if (add_overflow(te->hdr_size, te->data_size, &sz) || 290 add_with_round_up_overflow((uintptr_t)te, sz, 291 TRANSFER_LIST_GRANULE, &old_ev)) { 292 return false; 293 } 294 if (add_overflow(te->hdr_size, new_data_size, &sz) || 295 add_with_round_up_overflow((uintptr_t)te, sz, 296 TRANSFER_LIST_GRANULE, &new_ev)) { 297 return false; 298 } 299 300 if (new_ev > old_ev) { 301 // move distance should be roundup 302 // to meet the requirement of TE data max alignment 303 // ensure that the increased size doesn't exceed 304 // the max size of TL 305 mov_dis = new_ev - old_ev; 306 if (round_up_overflow(mov_dis, 1 << tl->alignment, 307 &mov_dis) || tl->size + mov_dis > tl->max_size) { 308 return false; 309 } 310 ru_new_ev = old_ev + mov_dis; 311 memmove((void *)ru_new_ev, (void *)old_ev, tl_old_ev - old_ev); 312 tl->size += mov_dis; 313 gap = ru_new_ev - new_ev; 314 } else { 315 gap = old_ev - new_ev; 316 } 317 318 if (gap >= sizeof(*dummy_te)) { 319 // create a dummy TE to fill up the gap 320 dummy_te = (struct transfer_list_entry *)new_ev; 321 dummy_te->tag_id = TL_TAG_EMPTY; 322 dummy_te->reserved0 = 0; 323 dummy_te->hdr_size = sizeof(*dummy_te); 324 dummy_te->data_size = gap - sizeof(*dummy_te); 325 } 326 327 te->data_size = new_data_size; 328 329 transfer_list_update_checksum(tl); 330 return true; 331 } 332 333 /******************************************************************************* 334 * Remove a specified transfer entry from a transfer list 335 * Return true on success or false on error 336 ******************************************************************************/ 337 bool transfer_list_rem(struct transfer_list_header *tl, 338 struct transfer_list_entry *te) 339 { 340 if (!tl || !te || (uintptr_t)te > (uintptr_t)tl + tl->size) { 341 return false; 342 } 343 te->tag_id = TL_TAG_EMPTY; 344 te->reserved0 = 0; 345 transfer_list_update_checksum(tl); 346 return true; 347 } 348 349 /******************************************************************************* 350 * Add a new transfer entry into a transfer list 351 * Compliant to 2.4.3 of Firmware handoff specification (v0.9) 352 * Return pointer to the added transfer entry or NULL on error 353 ******************************************************************************/ 354 struct transfer_list_entry *transfer_list_add(struct transfer_list_header *tl, 355 uint16_t tag_id, 356 uint32_t data_size, 357 const void *data) 358 { 359 uintptr_t max_tl_ev, tl_ev, ev; 360 struct transfer_list_entry *te = NULL; 361 uint8_t *te_data = NULL; 362 size_t sz = 0; 363 364 if (!tl) { 365 return NULL; 366 } 367 368 max_tl_ev = (uintptr_t)tl + tl->max_size; 369 tl_ev = (uintptr_t)tl + tl->size; 370 ev = tl_ev; 371 372 // skip the step 1 (optional step) 373 // new TE will be added into the tail 374 if (add_overflow(sizeof(*te), data_size, &sz) || 375 add_with_round_up_overflow(ev, sz, 376 TRANSFER_LIST_GRANULE, &ev) || ev > max_tl_ev) { 377 return NULL; 378 } 379 380 te = (struct transfer_list_entry *)tl_ev; 381 te->tag_id = tag_id; 382 te->reserved0 = 0; 383 te->hdr_size = sizeof(*te); 384 te->data_size = data_size; 385 tl->size += ev - tl_ev; 386 387 if (data) { 388 // get TE data pointer 389 te_data = transfer_list_entry_data(te); 390 if (!te_data) { 391 return NULL; 392 } 393 memmove(te_data, data, data_size); 394 } 395 396 transfer_list_update_checksum(tl); 397 398 return te; 399 } 400 401 /******************************************************************************* 402 * Add a new transfer entry into a transfer list with specified new data 403 * alignment requirement 404 * Compliant to 2.4.4 of Firmware handoff specification (v0.9) 405 * Return pointer to the added transfer entry or NULL on error 406 ******************************************************************************/ 407 struct transfer_list_entry *transfer_list_add_with_align( 408 struct transfer_list_header *tl, 409 uint16_t tag_id, uint32_t data_size, 410 const void *data, uint8_t alignment) 411 { 412 struct transfer_list_entry *te = NULL; 413 uintptr_t tl_ev, ev, new_tl_ev; 414 size_t dummy_te_data_sz = 0; 415 416 if (!tl) { 417 return NULL; 418 } 419 420 tl_ev = (uintptr_t)tl + tl->size; 421 ev = tl_ev + sizeof(struct transfer_list_entry); 422 423 if (!is_aligned(ev, 1 << alignment)) { 424 // TE data address is not aligned to the new alignment 425 // fill the gap with an empty TE as a placeholder before 426 // adding the desire TE 427 new_tl_ev = round_up(ev, 1 << alignment) - 428 sizeof(struct transfer_list_entry); 429 dummy_te_data_sz = new_tl_ev - tl_ev - 430 sizeof(struct transfer_list_entry); 431 if (!transfer_list_add(tl, TL_TAG_EMPTY, dummy_te_data_sz, 432 NULL)) { 433 return NULL; 434 } 435 } 436 437 te = transfer_list_add(tl, tag_id, data_size, data); 438 439 if (alignment > tl->alignment) { 440 tl->alignment = alignment; 441 transfer_list_update_checksum(tl); 442 } 443 444 return te; 445 } 446 447 /******************************************************************************* 448 * Search for an existing transfer entry with the specified tag id from a 449 * transfer list 450 * Return pointer to the found transfer entry or NULL on error 451 ******************************************************************************/ 452 struct transfer_list_entry *transfer_list_find(struct transfer_list_header *tl, 453 uint16_t tag_id) 454 { 455 struct transfer_list_entry *te = NULL; 456 457 do { 458 te = transfer_list_next(tl, te); 459 } while (te && (te->tag_id != tag_id || te->reserved0 != 0)); 460 461 return te; 462 } 463 464 /******************************************************************************* 465 * Retrieve the data pointer of a specified transfer entry 466 * Return pointer to the transfer entry data or NULL on error 467 ******************************************************************************/ 468 void *transfer_list_entry_data(struct transfer_list_entry *entry) 469 { 470 if (!entry) { 471 return NULL; 472 } 473 return (uint8_t *)entry + entry->hdr_size; 474 } 475