When we enabled SMMU in Dxx board, we found that the performance of 82599 pluged
in PCIe slot is very bad. LeiZhen and I spent some to debug this problem. This
document just shares the related results and information.
test scenarios and results
1 | +----------------+ +---------------+ |
Hardware topology as showed above. In order to use SMMU to translate data from
82599 to DDR, we need enable SMMU node in ACPI table.[1]
Then boot up two Dxx boards connected by two 82599 networking cards. When using
iperf to test the performance between two 82599 networking cards, it is very bad,
nearly 100Mbps.[2]
analysis
The only difference is disable SMMU and enable SMMU. So the difference is we use
diffferent DMA callbacks.
We can see arch/arm64/mm/dma-mapping.c, when configuring CONFIG_IOMMU_DMA,
callbacks in struct dma_map_ops iommu_dma_ops will be used.
And we also know when 82599 sending/receiving packages, its driver will call
ixgbe_tx_map/ixgbe_alloc_mapped_page to allocate DMA memory function which will
finally call map_page in iommu_dma_ops when SMMU is enable. So we guess there
is something wrong with the map_page here.
So we should analyze related function to find the hot point. Here we firstly use
ftrace to confirm our idea, then use perf to locate the hot point explicitly.
ftrace
We can use function profiling in ftrace to see durations of related function.
please refer[3] to know how to use ftrace function profiling.Here we get:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2110)<...>-4313 | | ixgbe_xmit_frame_ring() {
10)<...>-4313 | | __iommu_map_page() {
10)<...>-4313 | 0.080 us | dma_direction_to_prot();
10)<...>-4313 | | iommu_dma_map_page() {
10)<...>-4313 | | __iommu_dma_map() {
10)<...>-4313 | 0.480 us | iommu_get_domain_for_dev();
10)<...>-4313 | | __alloc_iova() {
10)<...>-4313 | | alloc_iova() {
10)<...>-4313 | | kmem_cache_alloc() {
10)<...>-4313 | | __slab_alloc.isra.21()
10)<...>-4313 | 0.040 us | memcg_kmem_put_cache();
10)<...>-4313 | + 16.160 us | }
[...]
10)<...>-4313 | 0.120 us | _raw_spin_lock_irqsave();
10)<...>-4313 | | _raw_spin_unlock_irqrestore() {
10)<...>-4313 | ==========> |
10)<...>-4313 | | gic_handle_irq()
10)<...>-4313 | <========== |
10)<...>-4313 | + 88.620 us | }
10)<...>-4313 | ! 679.760 us | }
10)<...>-4313 | ! 680.480 us | }Most time has been spent in alloc_iova.
perf
Sadly, there was no perf(PMU hardware event) support in ACPI in plinth
kernel :( So we directly set PMU related registers to get how many CPU cycles
each function has spent.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/* Firstly call this init function to init PMU */
static void pm_cycle_init(void)
{
u64 val;
asm volatile("mrs %0, pmccfiltr_el0" : "=r" (val));
if (val & ((u64)1 << 31)) {
val &= ~((u64)1 << 31);
asm volatile("msr pmccfiltr_el0, %0" :: "r" (val));
dsb(sy);
asm volatile("mrs %0, pmccfiltr_el0" : "=r" (val));
}
asm volatile("mrs %0, pmcntenset_el0" : "=r" (val));
if (!(val & ((u64)1 << 31))) {
val |= ((u64)1 << 31);
asm volatile("msr pmcntenset_el0, %0" :: "r" (val));
dsb(sy);
asm volatile("mrs %0, pmcntenset_el0" : "=r" (val));
}
asm volatile("mrs %0, pmcr_el0" : "=r" (val));
if (!(val & ((u64)1 << 6))) {
val |= ((u64)1 << 6) | 0x1;
asm volatile("msr pmcr_el0, %0" :: "r" (val));
dsb(sy);
asm volatile("mrs %0, pmcr_el0" : "=r" (val));
}
}
/* Get the CPU cycles in PMU counter */
u64 pm_cycle_get(void)
{
u64 val;
asm volatile("mrs %0, pmccntr_el0" : "=r" (val));
return val;
}
EXPORT_SYMBOL(pm_cycle_get);Using above debug functions, we found almost 600000 CPU cycles will happen in
a while loop in function __alloc_and_insert_iova_range. If CPU frequency is
2G Hz, then 600000 CPU cycles is 300us! This is the hot point.We found it will loop almost 10000 times in above while loop!
Code analysis
Firstly, this DMA software modules is like this:
1
2VA = kmalloc();
IOVA = dma_map_function(PA = fun(VA));Firstly, allocate memory for DMA memory and map a VA which can be used by CPU,
Then, build map between IOVA and PA in dma map function. In the case of SMMU
enable, .map_page(__iommu_map_page) in iommu_dma_ops will be call to build
above map.Then common function iommu_dma_map_page in drivers/iommu/dma-iommu.c will be
called. There are two steps in above function: 1. allocate iova, this is a
common function; 2. build map between IOVA and PA, this is SMMU specific
function.The hot point is in the point 1 above, so we need understand the module of
how to allocate iova. A red black tree in iova_domain is used to store all iova
range in system, after allocating or freeing an iova range, an iova range
should be inserted or remove from above red black tree. Now we allocate the
iova range from the end of the iova domain, for 32 DMA mask, it is 0xffffffff,
for 64bit DMA mask it is 0xffffffff_ffffffff. There is a cache for 32bit DMA
mask to store the iova range in last time, but for 64bit DMA MASK, there is
no such cache. so for 64bit DMA, when we want to allocate a DMA range in
iova domain, we have to search from the 0xffffffff_ffffffff. If we already allocate
a lot iova range, then we have to search all iova range allocated before.
solution
So we can fix this bug like this:
1 | diff --git a/drivers/iommu/iova.c b/drivers/iommu/iova.c |
above solution just adds another cache for 64bit DMA Mask.
But now Linux kernel community just merged a PATCH:
iommu/dma: Implement PCI allocation optimisation
into mainline kernel.
This patch just castes 64bit DMA mask to 32bit DMA mask, so we can still use
32bit DMA cache to improve the performance.
NOTE: but if this we can not allocate a 64bit iova to a PCIe device’s DMA target
address. This is a problem :(
problem
Performance
After SMMU enable and applying above patch, 82599 performance is 7.5Gbps,
only 80% performance comparing SMMU disable. We need check if this is correct
considering both hardware and software limitation.NIC panic
After SMMU enable and applying above patch, xxx net will panic :( should fix this.
(p.s. already find where the problem is, xxx net dma map once, but unmap multiple
times)
reference
[1] xxx
[2] JIRA bug
[3] https://lwn.net/Articles/370423/
cd /sys/kernel/debug/tracing
echo ixgbe_* > set_graph_function
echo function_graph > current_tracer
cat trace > ~/smmu_test