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

Configure Feed

Select the types of activity you want to include in your feed.

i3c: master: Add helpers for DMA mapping and bounce buffer handling

Some I3C controllers such as MIPI I3C HCI may pad the last DWORD (32-bit)
with stale data from the RX FIFO in DMA transfers if the receive length
is not DWORD aligned and when the device DMA is IOMMU mapped.

In such a case, a properly sized bounce buffer is required in order to
avoid possible data corruption. In a review discussion, proposal was to
have a common helpers in I3C core for DMA mapping and bounce buffer
handling.

Drivers may use the helper i3c_master_dma_map_single() to map a buffer
for a DMA transfer. It internally allocates a bounce buffer if buffer is
not DMA'able or when the driver requires it for a transfer.

Helper i3c_master_dma_unmap_single() does the needed cleanups and
data copying from the bounce buffer.

Signed-off-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://lore.kernel.org/r/20250822105630.2820009-2-jarkko.nikula@linux.intel.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>

authored by

Jarkko Nikula and committed by
Alexandre Belloni
f8d9e56a d515503f

+100
+74
drivers/i3c/master.c
··· 8 8 #include <linux/atomic.h> 9 9 #include <linux/bug.h> 10 10 #include <linux/device.h> 11 + #include <linux/dma-mapping.h> 11 12 #include <linux/err.h> 12 13 #include <linux/export.h> 13 14 #include <linux/kernel.h> ··· 1727 1726 return 0; 1728 1727 } 1729 1728 EXPORT_SYMBOL_GPL(i3c_master_do_daa); 1729 + 1730 + /** 1731 + * i3c_master_dma_map_single() - Map buffer for single DMA transfer 1732 + * @dev: device object of a device doing DMA 1733 + * @buf: destination/source buffer for DMA 1734 + * @len: length of transfer 1735 + * @force_bounce: true, force to use a bounce buffer, 1736 + * false, function will auto check is a bounce buffer required 1737 + * @dir: DMA direction 1738 + * 1739 + * Map buffer for a DMA transfer and allocate a bounce buffer if required. 1740 + * 1741 + * Return: I3C DMA transfer descriptor or NULL in case of error. 1742 + */ 1743 + struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf, 1744 + size_t len, bool force_bounce, enum dma_data_direction dir) 1745 + { 1746 + struct i3c_dma *dma_xfer __free(kfree) = NULL; 1747 + void *bounce __free(kfree) = NULL; 1748 + void *dma_buf = buf; 1749 + 1750 + dma_xfer = kzalloc(sizeof(*dma_xfer), GFP_KERNEL); 1751 + if (!dma_xfer) 1752 + return NULL; 1753 + 1754 + dma_xfer->dev = dev; 1755 + dma_xfer->buf = buf; 1756 + dma_xfer->dir = dir; 1757 + dma_xfer->len = len; 1758 + dma_xfer->map_len = len; 1759 + 1760 + if (is_vmalloc_addr(buf)) 1761 + force_bounce = true; 1762 + 1763 + if (force_bounce) { 1764 + dma_xfer->map_len = ALIGN(len, cache_line_size()); 1765 + if (dir == DMA_FROM_DEVICE) 1766 + bounce = kzalloc(dma_xfer->map_len, GFP_KERNEL); 1767 + else 1768 + bounce = kmemdup(buf, dma_xfer->map_len, GFP_KERNEL); 1769 + if (!bounce) 1770 + return NULL; 1771 + dma_buf = bounce; 1772 + } 1773 + 1774 + dma_xfer->addr = dma_map_single(dev, dma_buf, dma_xfer->map_len, dir); 1775 + if (dma_mapping_error(dev, dma_xfer->addr)) 1776 + return NULL; 1777 + 1778 + dma_xfer->bounce_buf = no_free_ptr(bounce); 1779 + return no_free_ptr(dma_xfer); 1780 + } 1781 + EXPORT_SYMBOL_GPL(i3c_master_dma_map_single); 1782 + 1783 + /** 1784 + * i3c_master_dma_unmap_single() - Unmap buffer after DMA 1785 + * @dma_xfer: DMA transfer and mapping descriptor 1786 + * 1787 + * Unmap buffer and cleanup DMA transfer descriptor. 1788 + */ 1789 + void i3c_master_dma_unmap_single(struct i3c_dma *dma_xfer) 1790 + { 1791 + dma_unmap_single(dma_xfer->dev, dma_xfer->addr, 1792 + dma_xfer->map_len, dma_xfer->dir); 1793 + if (dma_xfer->bounce_buf) { 1794 + if (dma_xfer->dir == DMA_FROM_DEVICE) 1795 + memcpy(dma_xfer->buf, dma_xfer->bounce_buf, 1796 + dma_xfer->len); 1797 + kfree(dma_xfer->bounce_buf); 1798 + } 1799 + kfree(dma_xfer); 1800 + } 1801 + EXPORT_SYMBOL_GPL(i3c_master_dma_unmap_single); 1730 1802 1731 1803 /** 1732 1804 * i3c_master_set_info() - set master device information
+26
include/linux/i3c/master.h
··· 558 558 #define i3c_bus_for_each_i3cdev(bus, dev) \ 559 559 list_for_each_entry(dev, &(bus)->devs.i3c, common.node) 560 560 561 + /** 562 + * struct i3c_dma - DMA transfer and mapping descriptor 563 + * @dev: device object of a device doing DMA 564 + * @buf: destination/source buffer for DMA 565 + * @len: length of transfer 566 + * @map_len: length of DMA mapping 567 + * @addr: mapped DMA address for a Host Controller Driver 568 + * @dir: DMA direction 569 + * @bounce_buf: an allocated bounce buffer if transfer needs it or NULL 570 + */ 571 + struct i3c_dma { 572 + struct device *dev; 573 + void *buf; 574 + size_t len; 575 + size_t map_len; 576 + dma_addr_t addr; 577 + enum dma_data_direction dir; 578 + void *bounce_buf; 579 + }; 580 + 561 581 int i3c_master_do_i2c_xfers(struct i3c_master_controller *master, 562 582 const struct i2c_msg *xfers, 563 583 int nxfers); ··· 595 575 int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, 596 576 u8 addr); 597 577 int i3c_master_do_daa(struct i3c_master_controller *master); 578 + struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *ptr, 579 + size_t len, bool force_bounce, 580 + enum dma_data_direction dir); 581 + void i3c_master_dma_unmap_single(struct i3c_dma *dma_xfer); 582 + DEFINE_FREE(i3c_master_dma_unmap_single, void *, 583 + if (_T) i3c_master_dma_unmap_single(_T)) 598 584 599 585 int i3c_master_set_info(struct i3c_master_controller *master, 600 586 const struct i3c_device_info *info);