Фу Ханьцзе hankf@amd.com
Две недели назад кто-то спросил об операциях кэширования и согласованности DMA под DMA. Я уже видел этот код по частям. Временно ищу, но пока не нашел.
За последние два дня я разобрался с процессом вызова и обнаружил использование dma-coherent. В документации Linux не объясняется подробно использование dma-coherent. Согласно коду, если в дереве устройств dma присутствует dma-coherent, Linux будет думать, что оборудование будет поддерживать согласованность кэша и не будет выполнять операции кэширования во время работы dma.
Драйверы устройств обычно вызывают dma_map_single()/dma_unmap_single() для обработки кеша. При вызове функции dma_map_single необходимо указать направление DMA, DMA_TO_DEVICE или DMA_FROM_DEVICE. Linux аннулирует или очистит кеш в зависимости от значения направления.
В функции macb_tx_map() драйверов\net\ethernet\cadence\macb_main.c вызовите dma_map_single() для обновления кэша, а macb_tx_unmap() из macb_tx_interrupt() затем вызовет dma_unmap_single().
Код упрощается следующим образом:
macb_tx_map( )
{
.......
mapping = dma_map_single(&bp->pdev->dev,
skb->data + offset,
size, DMA_TO_DEVICE);
.......
}
macb_tx_unmap( )
{
.......
dma_unmap_single(&bp->pdev->dev, tx_skb->mapping,
tx_skb->size, DMA_TO_DEVICE);
.......
}
gem_rx( )
{
.......
dma_unmap_single(&bp->pdev->dev, addr,
bp->rx_buffer_size, DMA_FROM_DEVICE);
.......
}
gem_rx_refill()
{
.......
/* now fill corresponding descriptor entry */
paddr = dma_map_single(&bp->pdev->dev, skb->data,
bp->rx_buffer_size,
DMA_FROM_DEVICE);
.......
}
И dma_map_single(), и dma_unmap_single() определены в include\linux\dma-mapping.h. Если нет особых обстоятельств, будут вызваны dma_direct_map_page() и dma_direct_unmap_page(). К особым случаям для Arm64 относятся виртуальные машины iommu и Xen. Виртуальные машины iommu и Xen должны предоставлять dma_map_ops, поэтому используйте функции map и unmap. dma_map_ops iommu — это iommu_dma_ops, определенный в драйверах\iommu\Dma-iommu.c. dma_map_ops iommu — это xen_swiotlb_dma_ops, определенный в драйверах/xen/swiotlb-xen.c.
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
#define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,
size_t size, enum dma_data_direction dir, unsigned long attrs)
{
debug_dma_map_single(dev, ptr, size);
return dma_map_page_attrs(dev, virt_to_page(ptr), offset_in_page(ptr),
size, dir, attrs);
}
static inline void dma_unmap_single_attrs(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir, unsigned long attrs)
{
return dma_unmap_page_attrs(dev, addr, size, dir, attrs);
}
static inline dma_addr_t dma_map_page_attrs(struct device *dev,
struct page *page, size_t offset, size_t size,
enum dma_data_direction dir, unsigned long attrs)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
dma_addr_t addr;
BUG_ON(!valid_dma_direction(dir));
if (dma_is_direct(ops))
addr = dma_direct_map_page(dev, page, offset, size, dir, attrs);
else
addr = ops->map_page(dev, page, offset, size, dir, attrs);
debug_dma_map_page(dev, page, offset, size, dir, addr);
return addr;
}
static inline void dma_unmap_page_attrs(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir, unsigned long attrs)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
BUG_ON(!valid_dma_direction(dir));
if (dma_is_direct(ops))
dma_direct_unmap_page(dev, addr, size, dir, attrs);
else if (ops->unmap_page)
ops->unmap_page(dev, addr, size, dir, attrs);
debug_dma_unmap_page(dev, addr, size, dir);
}
dma_direct_map_page(), dma_direct_unmap_page() определены в kernel\dma\direct.c.
dma_addr_t dma_direct_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
phys_addr_t phys = page_to_phys(page) + offset;
dma_addr_t dma_addr = phys_to_dma(dev, phys);
if (unlikely(!dma_direct_possible(dev, dma_addr, size)) &&
!swiotlb_map(dev, &phys, &dma_addr, size, dir, attrs)) {
report_addr(dev, dma_addr, size);
return DMA_MAPPING_ERROR;
}
if (!dev_is_dma_coherent(dev) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
arch_sync_dma_for_device(dev, phys, size, dir);
return dma_addr;
}
EXPORT_SYMBOL(dma_direct_map_page);
void dma_direct_unmap_page(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir, unsigned long attrs)
{
phys_addr_t phys = dma_to_phys(dev, addr);
if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC))
dma_direct_sync_single_for_cpu(dev, addr, size, dir);
if (unlikely(is_swiotlb_buffer(phys)))
swiotlb_tbl_unmap_single(dev, phys, size, size, dir, attrs);
}
EXPORT_SYMBOL(dma_direct_unmap_page);
void dma_direct_sync_single_for_cpu(struct device *dev,
dma_addr_t addr, size_t size, enum dma_data_direction dir)
{
phys_addr_t paddr = dma_to_phys(dev, addr);
if (!dev_is_dma_coherent(dev)) {
arch_sync_dma_for_cpu(dev, paddr, size, dir);
arch_sync_dma_for_cpu_all(dev);
}
if (unlikely(is_swiotlb_buffer(paddr)))
swiotlb_tbl_sync_single(dev, paddr, size, dir, SYNC_FOR_CPU);
}
Прослеживая весь путь, dma_map_single() в конечном итоге вызовет Arch_sync_dma_for_device(), dma_unmap_single() в конечном итоге вызовет Arch_sync_dma_for_cpu() и Arch_sync_dma_for_cpu_all(). А Arch_sync_dma_for_cpu_all() — пустая функция для Arm64.
Arch_sync_dma_for_device/arch_sync_dma_for_cpu определяется в файле Arch\arm64\mm\dma-mapping.c.
void arch_sync_dma_for_device(struct device *dev, phys_addr_t paddr,
size_t size, enum dma_data_direction dir)
{
__dma_map_area(phys_to_virt(paddr), size, dir);
}
void arch_sync_dma_for_cpu(struct device *dev, phys_addr_t paddr,
size_t size, enum dma_data_direction dir)
{
__dma_unmap_area(phys_to_virt(paddr), size, dir);
}
Определение __dma_map_area/__dma_unmap_area находится в файле Arch\arm64\mm\cache.S.
Все реализации сборки также находятся в файлеarch\arm64\mm\cache.S.
/*
* __dma_map_area(start, size, dir)
* - start - kernel virtual start address
* - size - size of region
* - dir - DMA direction
*/
ENTRY(__dma_map_area)
cmp w2, #DMA_FROM_DEVICE
b.eq __dma_inv_area
b __dma_clean_area
ENDPIPROC(__dma_map_area)
/*
* __dma_unmap_area(start, size, dir)
* - start - kernel virtual start address
* - size - size of region
* - dir - DMA direction
*/
ENTRY(__dma_unmap_area)
cmp w2, #DMA_TO_DEVICE
b.ne __dma_inv_area
ret
ENDPIPROC(__dma_unmap_area)
Видно, что __dma_map_area, вызываемая функцией серии карт, если направление DMA_FROM_DEVICE, выполните __dma_inv_area, в противном случае выполните __dma_clean_area; __dma_unmap_area, вызываемая функциями unmap series, если направление не DMA_TO_DEVICE, выполните __dma_inv_area, в противном случае выполните __dma_clean_area;
Краткое изложение следующее:
Operation | map | unmap |
---|---|---|
DMA_FROM_DEVICE | __dma_inv_area | __dma_inv_area |
DMA_TO_DEVICE | __dma_clean_area | __dma_clean_area |
__dma_inv_area завершает операцию аннулирования и удаляет данные кэша. Его аннотация:
Ensure that any D-cache lines for the interval [kaddr, kaddr+size)
* are invalidated. Any partial lines at the ends of the interval are
* also cleaned to PoC to prevent data loss。
Что касается Invalidate, в руководстве ARM «Справочное руководство по архитектуре Arm для архитектуры A-профиля» объясняется следующее:
Invalidate A cache invalidate instruction ensures that updates made visible by observers that access memory
at the point to which the invalidate is defined, are made visible to an observer that controls the cache.
This might result in the loss of updates to the locations affected by the invalidate instruction that
have been written by observers that access the cache, if those updates have not been cleaned from
the cache since they were made.
If the address of an entry on which the invalidate instruction operates is Normal, Non-cacheable or
any type of Device memory then an invalidate instruction also ensures that this address is not
present in the cache.
__dma_clean_area завершает операцию очистки и обновляет полученные данные в DDR. Его аннотация:
Ensure that any D-cache lines for the interval [kaddr, kaddr+size)
* are cleaned to the PoC.
Что касается Clean, в руководстве ARM объясняется следующее:
Clean A cache clean instruction ensures that updates made by an observer that controls the cache are made
visible to other observers that can access memory at the point to which the instruction is performed.
Once the Clean has completed, the new memory values are guaranteed to be visible to the point to
which the instruction is performed, for example to the Point of Unification.
The cleaning of a cache entry from a cache can overwrite memory that has been written by another
observer only if the entry contains a location that has been written to by an observer in the
shareability domain of that memory location.
Перед отправкой Ethernet выполняется сопоставление DMA_TO_DEVICE; после отправки выполняется отметка DMA_TO_DEVICE; оба выполняют __dma_clean_area для обновления данных из кэша в DDR;
Перед приемом Ethernet выполняется карта DMA_FROM_DEVICE, выполняется __dma_inv_area, и данные кэша удаляются; после приема выполняется unmap DMA_FROM_DEVICE, выполняется __dma_clean_area, и данные обновляются из кэша в DDR.
Обновление таблицы:
Operation | map,for_device | unmap,for_cpu |
---|---|---|
DMA_FROM_DEVICE | Перед получением __dma_inv_area | После получения __dma_inv_area |
DMA_TO_DEVICE | Перед отправкой __dma_clean_area | После отправки __dma_clean_area |
Я так и не понял, почему в драйвере Linux есть две операции с кэшем, и название немного сбивает с толку: сначала сопоставить, а потом отключить. В Standalone драйвере вам нужно только очистить кэш перед отправкой и аннулировать кэш после получения, нет необходимости оперировать кэшем после отправки и перед получением;
Немного понятно, что операция приема выполняется дважды. Возможно, другие модули вызывают повторную загрузку старых данных в кэш. Трудно понять, что отправка и выполнение двух операций с кэшем. Возможно, здесь также были изменены настройки таблицы MMU.
Раньше я также имел дело с проблемой. Предиктивное выполнение Arm приведет к чтению DDR, который не используется программным обеспечением. Чтобы предотвратить эту ситуацию, запись соответствующего адреса должна быть установлена в таблице mmu как полностью недействительная. Возможно, Linux раньше сталкивался с некоторыми проблемами и перешел на эту операцию.
Атрибут «dma-coherent» можно настроить в дереве устройств DMA.
of_dma_is_coherent() в файле driver\of\address.c считывает атрибут «dma-coherent».
bool of_dma_is_coherent(struct device_node *np)
{
struct device_node *node = of_node_get(np);
while (node) {
if (of_property_read_bool(node, "dma-coherent")) {
of_node_put(node);
return true;
}
node = of_get_next_parent(node);
}
of_node_put(node);
return false;
}
драйверы\of\Device.cintermediateof_dma_configure( ) вызовы of_dma_is_coherent( ) считывает атрибут «dma-coherent», а затем вызывает Arch_setup_dma_ops( ),Сохранить в переменной“dev->dma_coherent”середина。
/**
* of_dma_configure - Setup DMA configuration
* @dev: Device to apply DMA configuration
* @np: Pointer to OF node having DMA configuration
* @force_dma: Whether device is to be set up by of_dma_configure() even if
* DMA capability is not explicitly described by firmware.
*
* Try to get devices's DMA configuration from DT and update it
* accordingly.
*
* If platform code needs to use its own special DMA configuration, it
* can use a platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE events
* to fix up DMA configuration.
*/
int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
{
........
coherent = of_dma_is_coherent(np);
dev_dbg(dev, "device is%sdma coherent\n",
coherent ? " " : " not ");
iommu = of_iommu_configure(dev, np);
if (IS_ERR(iommu) && PTR_ERR(iommu) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_dbg(dev, "device is%sbehind an iommu\n",
iommu ? " " : " not ");
arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent);
return 0;
}
Arch\arm64\mm\Dma-mapping.cсерединаизarch_setup_dma_ops( ),поставить настройки Сохранить в переменной“dev->dma_coherent”середина。
void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
const struct iommu_ops *iommu, bool coherent)
{
int cls = cache_line_size_of_cpu();
WARN_TAINT(!coherent && cls > ARCH_DMA_MINALIGN,
TAINT_CPU_OUT_OF_SPEC,
"%s %s: ARCH_DMA_MINALIGN smaller than CTR_EL0.CWG (%d < %d)",
dev_driver_string(dev), dev_name(dev),
ARCH_DMA_MINALIGN, cls);
dev->dma_coherent = coherent;
if (iommu)
iommu_setup_dma_ops(dev, dma_base, size);
#ifdef CONFIG_XEN
if (xen_initial_domain())
dev->dma_ops = &xen_swiotlb_dma_ops;
#endif
в dma_direct_map_page и,Вызовите dev_is_dma_coherent().,Проверьте указанные выше переменныеdev->dma_coherent,Проверьте, поддерживается ли синхронизация аппаратного кэша. в случае,Операция кэширования не будет выполнена.
Определение dev_is_dma_coherent находится в файле include\linux\dma-noncoherent.h.
static inline bool dev_is_dma_coherent(struct device *dev)
{
return dev->dma_coherent;
}