• Bumyong Lee's avatar
    swiotlb: manipulate orig_addr when tlb_addr has offset · 5f89468e
    Bumyong Lee authored
    in case of driver wants to sync part of ranges with offset,
    swiotlb_tbl_sync_single() copies from orig_addr base to tlb_addr with
    offset and ends up with data mismatch.
    
    It was removed from
    "swiotlb: don't modify orig_addr in swiotlb_tbl_sync_single",
    but said logic has to be added back in.
    
    From Linus's email:
    "That commit which the removed the offset calculation entirely, because the old
    
            (unsigned long)tlb_addr & (IO_TLB_SIZE - 1)
    
    was wrong, but instead of removing it, I think it should have just
    fixed it to be
    
            (tlb_addr - mem->start) & (IO_TLB_SIZE - 1);
    
    instead. That way the slot offset always matches the slot index calculation."
    
    (Unfortunatly that broke NVMe).
    
    The use-case that drivers are hitting is as follow:
    
    1. Get dma_addr_t from dma_map_single()
    
    dma_addr_t tlb_addr = dma_map_single(dev, vaddr, vsize, DMA_TO_DEVICE);
    
        |<---------------vsize------------->|
        +-----------------------------------+
        |                                   | original buffer
        +-----------------------------------+
      vaddr
    
     swiotlb_align_offset
         |<----->|<---------------vsize------------->|
         +-------+-----------------------------------+
         |       |                                   | swiotlb buffer
         +-------+-----------------------------------+
              tlb_addr
    
    2. Do something
    3. Sync dma_addr_t through dma_sync_single_for_device(..)
    
    dma_sync_single_for_device(dev, tlb_addr + offset, size, DMA_TO_DEVICE);
    
      Error case.
        Copy data to original buffer but it is from base addr (instead of
      base addr + offset) in original buffer:
    
     swiotlb_align_offset
         |<----->|<- offset ->|<- size ->|
         +-------+-----------------------------------+
         |       |            |##########|           | swiotlb buffer
         +-------+-----------------------------------+
              tlb_addr
    
        |<- size ->|
        +-----------------------------------+
        |##########|                        | original buffer
        +-----------------------------------+
      vaddr
    
    The fix is to copy the data to the original buffer and take into
    account the offset, like so:
    
     swiotlb_align_offset
         |<----->|<- offset ->|<- size ->|
         +-------+-----------------------------------+
         |       |            |##########|           | swiotlb buffer
         +-------+-----------------------------------+
              tlb_addr
    
        |<- offset ->|<- size ->|
        +-----------------------------------+
        |            |##########|           | original buffer
        +-----------------------------------+
      vaddr
    
    [One fix which was Linus's that made more sense to as it created a
    symmetry would break NVMe. The reason for that is the:
     unsigned int offset = (tlb_addr - mem->start) & (IO_TLB_SIZE - 1);
    
    would come up with the proper offset, but it would lose the
    alignment (which this patch contains).]
    
    Fixes: 16fc3cef ("swiotlb: don't modify orig_addr in swiotlb_tbl_sync_single")
    Signed-off-by: default avatarBumyong Lee <bumyong.lee@samsung.com>
    Signed-off-by: default avatarChanho Park <chanho61.park@samsung.com>
    Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
    Reported-by: default avatarDominique MARTINET <dominique.martinet@atmark-techno.com>
    Reported-by: default avatarHoria Geantă <horia.geanta@nxp.com>
    Tested-by: default avatarHoria Geantă <horia.geanta@nxp.com>
    CC: stable@vger.kernel.org
    Signed-off-by: default avatarKonrad Rzeszutek Wilk <konrad.wilk@oracle.com>
    5f89468e
swiotlb.c 18.5 KB