Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iommu/dma: Implement scatterlist segment merging

Stop wasting IOVA space by over-aligning scatterlist segments for a
theoretical worst-case segment boundary mask, and instead take the real
limits into account to merge consecutive segments wherever appropriate,
so our callers can benefit from getting back nicely simplified lists.

This also represents the last piece of functionality wanted by users of
the current arch/arm implementation, thus brings us a small step closer
to converting that over to the common code.

Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: Joerg Roedel <jroedel@suse.de>

authored by

Robin Murphy and committed by
Joerg Roedel
809eac54 44549e8f

+61 -23
+61 -23
drivers/iommu/dma-iommu.c
··· 389 389 390 390 /* 391 391 * Prepare a successfully-mapped scatterlist to give back to the caller. 392 - * Handling IOVA concatenation can come later, if needed 392 + * 393 + * At this point the segments are already laid out by iommu_dma_map_sg() to 394 + * avoid individually crossing any boundaries, so we merely need to check a 395 + * segment's start address to avoid concatenating across one. 393 396 */ 394 397 static int __finalise_sg(struct device *dev, struct scatterlist *sg, int nents, 395 398 dma_addr_t dma_addr) 396 399 { 397 - struct scatterlist *s; 398 - int i; 400 + struct scatterlist *s, *cur = sg; 401 + unsigned long seg_mask = dma_get_seg_boundary(dev); 402 + unsigned int cur_len = 0, max_len = dma_get_max_seg_size(dev); 403 + int i, count = 0; 399 404 400 405 for_each_sg(sg, s, nents, i) { 401 - /* Un-swizzling the fields here, hence the naming mismatch */ 402 - unsigned int s_offset = sg_dma_address(s); 406 + /* Restore this segment's original unaligned fields first */ 407 + unsigned int s_iova_off = sg_dma_address(s); 403 408 unsigned int s_length = sg_dma_len(s); 404 - unsigned int s_dma_len = s->length; 409 + unsigned int s_iova_len = s->length; 405 410 406 - s->offset += s_offset; 411 + s->offset += s_iova_off; 407 412 s->length = s_length; 408 - sg_dma_address(s) = dma_addr + s_offset; 409 - dma_addr += s_dma_len; 413 + sg_dma_address(s) = DMA_ERROR_CODE; 414 + sg_dma_len(s) = 0; 415 + 416 + /* 417 + * Now fill in the real DMA data. If... 418 + * - there is a valid output segment to append to 419 + * - and this segment starts on an IOVA page boundary 420 + * - but doesn't fall at a segment boundary 421 + * - and wouldn't make the resulting output segment too long 422 + */ 423 + if (cur_len && !s_iova_off && (dma_addr & seg_mask) && 424 + (cur_len + s_length <= max_len)) { 425 + /* ...then concatenate it with the previous one */ 426 + cur_len += s_length; 427 + } else { 428 + /* Otherwise start the next output segment */ 429 + if (i > 0) 430 + cur = sg_next(cur); 431 + cur_len = s_length; 432 + count++; 433 + 434 + sg_dma_address(cur) = dma_addr + s_iova_off; 435 + } 436 + 437 + sg_dma_len(cur) = cur_len; 438 + dma_addr += s_iova_len; 439 + 440 + if (s_length + s_iova_off < s_iova_len) 441 + cur_len = 0; 410 442 } 411 - return i; 443 + return count; 412 444 } 413 445 414 446 /* ··· 478 446 struct scatterlist *s, *prev = NULL; 479 447 dma_addr_t dma_addr; 480 448 size_t iova_len = 0; 449 + unsigned long mask = dma_get_seg_boundary(dev); 481 450 int i; 482 451 483 452 /* 484 453 * Work out how much IOVA space we need, and align the segments to 485 454 * IOVA granules for the IOMMU driver to handle. With some clever 486 455 * trickery we can modify the list in-place, but reversibly, by 487 - * hiding the original data in the as-yet-unused DMA fields. 456 + * stashing the unaligned parts in the as-yet-unused DMA fields. 488 457 */ 489 458 for_each_sg(sg, s, nents, i) { 490 - size_t s_offset = iova_offset(iovad, s->offset); 459 + size_t s_iova_off = iova_offset(iovad, s->offset); 491 460 size_t s_length = s->length; 461 + size_t pad_len = (mask - iova_len + 1) & mask; 492 462 493 - sg_dma_address(s) = s_offset; 463 + sg_dma_address(s) = s_iova_off; 494 464 sg_dma_len(s) = s_length; 495 - s->offset -= s_offset; 496 - s_length = iova_align(iovad, s_length + s_offset); 465 + s->offset -= s_iova_off; 466 + s_length = iova_align(iovad, s_length + s_iova_off); 497 467 s->length = s_length; 498 468 499 469 /* 500 - * The simple way to avoid the rare case of a segment 501 - * crossing the boundary mask is to pad the previous one 502 - * to end at a naturally-aligned IOVA for this one's size, 503 - * at the cost of potentially over-allocating a little. 470 + * Due to the alignment of our single IOVA allocation, we can 471 + * depend on these assumptions about the segment boundary mask: 472 + * - If mask size >= IOVA size, then the IOVA range cannot 473 + * possibly fall across a boundary, so we don't care. 474 + * - If mask size < IOVA size, then the IOVA range must start 475 + * exactly on a boundary, therefore we can lay things out 476 + * based purely on segment lengths without needing to know 477 + * the actual addresses beforehand. 478 + * - The mask must be a power of 2, so pad_len == 0 if 479 + * iova_len == 0, thus we cannot dereference prev the first 480 + * time through here (i.e. before it has a meaningful value). 504 481 */ 505 - if (prev) { 506 - size_t pad_len = roundup_pow_of_two(s_length); 507 - 508 - pad_len = (pad_len - iova_len) & (pad_len - 1); 482 + if (pad_len && pad_len < s_length - 1) { 509 483 prev->length += pad_len; 510 484 iova_len += pad_len; 511 485 }