* [PATCH] ata: libahci: fix panic when accessing ports beyond MMIO region
@ 2026-04-22 8:03 dayou5941
2026-04-22 14:36 ` Niklas Cassel
0 siblings, 1 reply; 4+ messages in thread
From: dayou5941 @ 2026-04-22 8:03 UTC (permalink / raw)
To: dlemoal, cassel; +Cc: linux-ide, linux-kernel, liyouhong
From: liyouhong <liyouhong@kylinos.cn>
Commit 18ee7c49f75b ("ata: ahci: Introduce firmware-specific caps
initialization") introduced a regression where the driver would
attempt to access port registers beyond the mapped MMIO region when
HOST_PORTS_IMPL and HOST_CAP contains invalid values.
This occurs in scenarios where:
1. The AHCI controller is disabled by BIOS, causing registers to read
as 0xFFFFFFFF.
2. HOST_PORTS_IMPL indicates more ports than physically implemented.
3. The actual MMIO region is smaller than needed for the indicated
ports.
When the driver iterates through ports indicated in HOST_PORTS_IMPL and
accesses PORT_CMD registers, it may calculate port offsets that exceed
the mapped MMIO region. On ARM64 systems, this causes a kernel panic.
when attempting to read from the unmapped address.
When mmio_size is 0x1000 and all ports need to be accessed.
The issue manifests as a panic in __raw_readl() when i=30, as the
calculated offset (0x1000) equals the typical MMIO region size (0x1000),
placing the access exactly at the boundary of the mapped region.
The log is as follows:
[ 0.389751][ T1] Unable to handle kernel paging request at virtual address ffff800082916018
[ 0.389752][ T1] Mem abort info:
[ 0.389753][ T1] ESR = 0x0000000096000007
[ 0.389754][ T1] EC = 0x25: D
ABT (current EL), IL = 32 bits
[ 0.389756][ T1] SET = 0, FnV = 0
[ 0.389757][ T1] EA = 0, S1PTW = 0
[ 0.389758][ T1] FSC = 0x07: level 3 translation fault
[ 0.389759][ T1] Data abort info:
[ 0.389760][ T1] ISV = 0, ISS = 0x00000007, ISS2 = 0x00000000
[ 0.389761][ T1] CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[ 0.389762][ T1] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[ 0.389764][ T1] swapper pgtable: 4k pages, 48-bit VAs, pgdp=00000000e16b1000
[ 0.389766][ T1] [ffff800082916018] pgd=10000000e264d003, p4d=10000000e264d003, pud=10000000e264e003, pmd=10000000e2653003, pte=0000000000000000
[ 0.389772][ T1] Internal error: Oops: 0000000096000007 [#1] SMP
[ 0.391912][ T1] Modules linked in:
[ 0.397407][ T71] nvme nvme0: allocated 16 MiB host memory buffer (1 segment).
[ 0.406067][ T71] nvme nvme0: 8/0/0 default/read/poll queues
[ 0.426950][ T12] nvme0n1: p1 p2 p3 p4 p5 p6
[ 0.427171][ T71] probe of 0000:01:00.0 returned 0 after 37797 usecs
[ 0.428770][ T80] Freeing initrd memory: 39012K
[ 12.823718][ T1] CPU: 1 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.0.0-dirty #3 PREEMPTLAZY
[ 12.832490][ T1] Hardware name: ISOFTSTONE COMPUTER TianYaoW600 Series/EM_FC19M, BIOS KL4.2B.EM.D.R003.RTHF.260414.R 04/14/2026 16:26:14
[ 12.844992][ T1] pstate: 00400009 (nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 12.852635][ T1] pc : ahci_init_one+0x6a8/0xf68
[ 12.857417][ T1] lr : ahci_init_one+0x668/0xf68
[ 12.862194][ T1] sp : ffff8000828aba20
[ 12.866189][ T1] x29: ffff8000828aba20 x28: 000000000000001e x27: ffff800081742300
[ 12.874007][ T1] x26: ffff0020289b8000 x25: 0000000000000001 x24: ffff002020f710d0
[ 12.881823][ T1] x23: 0000000000000005 x22: ffff002026d9ee00 x21: ffff002026d9f280
[ 12.889639][ T1] x20: ffff002020f71000 x19: 0000000000000000 x18: 00000000ffffffff
[ 12.897456][ T1] x17: ffff002021c7c000 x16: ffff002020d6ee00 x15: ffff8000828ab850
[ 12.905272][ T1] x14: ffff0020289ba42c x13: ffff0020289ba3f9 x12: 0101010101010101
[ 12.913088][ T1] x11: 7f7f7f7f7f7f7f7f x10: 0000000000000078 x9 : ffff800080bdf1b0
[ 12.920905][ T1] x8 : 0000000000000030 x7 : 0000000000000008 x6 : ffff800081271928
[ 12.928721][ T1] x5 : 0000000000000030 x4 : 0000000000000030 x3 : 0000000000000000
[ 12.936537][ T1] x2 : ffff002026d9f280 x1 : 0000000000001018 x0 : ffff800082916018
[ 12.944354][ T1] Call trace:
[ 12.947481][ T1] ahci_init_one+0x6a8/0xf68 (P)
[ 12.952260][ T1] local_pci_probe+0x44/0xb0
[ 12.956693][ T1] pci_device_probe+0xd4/0x268
[ 12.961298][ T1] really_probe+0xc4/0x3e8
[ 12.965557][ T1] __driver_probe_device+0xd4/0x188
[ 12.970595][ T1] driver_probe_device+0x40/0x118
[ 12.975460][ T1] __driver_attach+0xe8/0x218
[ 12.979977][ T1] bus_for_each_dev+0x7c/0xe0
[ 12.984494][ T1] driver_attach+0x28/0x38
[ 12.988751][ T1] bus_add_driver+0x120/0x248
[ 12.993269][ T1] driver_register+0x60/0x128
[ 12.997787][ T1] __pci_register_driver+0x50/0x60
[ 13.002739][ T1] ahci_pci_driver_init+0x28/0x38
[ 13.007605][ T1] do_one_initcall+0x58/0x450
[ 13.012124][ T1] kernel_init_freeable+0x1fc/0x560
[ 13.017164][ T1] kernel_init+0x28/0x1f8
[ 13.021337][ T1] ret_from_fork+0x10/0x20
[ 13.025596][ T1] Code: 53196021 91046021 f9400840 8b010000 (b9400000)
[ 13.032370][ T1] ---[ end trace 0000000000000000 ]---
[ 13.037737][ T1] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b
[ 13.046077][ T1] SMP: stopping secondary CPUs
[ 13.050684][ T1] Kernel Offset: disabled
[ 13.054852][ T1] CPU features: 0x0000000,000e0005,40230521,0401720b
[ 13.061366][ T1] Memory Limit: none
[ 13.065102][ T1] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b ]---
Fix this by adding bounds checking in ahci_save_initial_config(). Before
accessing each port's PORT_CMD register, verify that the port's register
block offset is within the mapped MMIO region. Skip any ports that would
require accessing memory beyond the mapped region.
Fixes: 18ee7c49f75b ("ata: ahci: Introduce firmware-specific caps initialization")
Signed-off-by: liyouhong <liyouhong@kylinos.cn>
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 2d1ca9f2f546..69de3560aeec 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -468,6 +468,8 @@ void ahci_save_initial_config(struct device *dev, struct ahci_host_priv *hpriv)
void __iomem *port_mmio;
unsigned long port_map;
u32 cap, cap2, vers;
+ unsigned long long mmio_size = 0;
+ bool is_pci_dev = false;
int i;
/* make sure AHCI mode is enabled before accessing CAP */
@@ -595,6 +597,13 @@ void ahci_save_initial_config(struct device *dev, struct ahci_host_priv *hpriv)
hpriv->saved_port_map = port_map;
}
+ is_pci_dev = dev_is_pci(dev);
+ if (is_pci_dev) {
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ mmio_size = (unsigned long long)pci_resource_len(pdev, 5);
+ }
+
/*
* Preserve the ports capabilities defined by the platform. Note there
* is no need in storing the rest of the P#.CMD fields since they are
@@ -605,6 +614,29 @@ void ahci_save_initial_config(struct device *dev, struct ahci_host_priv *hpriv)
continue;
port_mmio = __ahci_port_base(hpriv, i);
+
+ /* Calculate offset from MMIO base */
+ unsigned long long port_offset = (unsigned long long)port_mmio -
+ (unsigned long long)mmio;
+ /* Check if port register block is within MMIO region */
+ if (is_pci_dev && port_offset >= mmio_size) {
+ /*
+ * Port registers exceed MMIO region boundary.
+ * Since ports are sequentially mapped (0x100 + i*0x80),
+ * all subsequent ports will also exceed the boundary.
+ *
+ * Update port_map to exclude this and all higher ports,
+ * then break out of the loop.
+ */
+ dev_warn(dev, "Port %d (offset 0x%llx) exceeds MMIO region (0x%llx),
+ truncating port map at port %d\n",
+ i, port_offset, mmio_size, i-1);
+
+ port_map = (1UL << i) - 1;
+ hpriv->saved_port_map = port_map;
+ break;
+ }
+
hpriv->saved_port_cap[i] =
readl(port_mmio + PORT_CMD) & PORT_CMD_CAP;
}
--
2.33.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] ata: libahci: fix panic when accessing ports beyond MMIO region
2026-04-22 8:03 [PATCH] ata: libahci: fix panic when accessing ports beyond MMIO region dayou5941
@ 2026-04-22 14:36 ` Niklas Cassel
[not found] ` <55809835.8838.19db9be1205.Coremail.dayou5941@163.com>
0 siblings, 1 reply; 4+ messages in thread
From: Niklas Cassel @ 2026-04-22 14:36 UTC (permalink / raw)
To: dayou5941; +Cc: dlemoal, linux-ide, linux-kernel, liyouhong
Hello liyouhong,
On Wed, Apr 22, 2026 at 04:03:22PM +0800, dayou5941@163.com wrote:
> From: liyouhong <liyouhong@kylinos.cn>
>
> Commit 18ee7c49f75b ("ata: ahci: Introduce firmware-specific caps
> initialization") introduced a regression where the driver would
> attempt to access port registers beyond the mapped MMIO region when
> HOST_PORTS_IMPL and HOST_CAP contains invalid values.
>
> This occurs in scenarios where:
> 1. The AHCI controller is disabled by BIOS, causing registers to read
> as 0xFFFFFFFF.
> 2. HOST_PORTS_IMPL indicates more ports than physically implemented.
> 3. The actual MMIO region is smaller than needed for the indicated
> ports.
I'm not a fan of this patch for two reasons:
1) The AHCI specification, 1.3.1, "10.1.1 Firmware Specific Initialization":
Firmware shall always initialize the following registers and values:
CAP.SSS (support for staggered spin-up)
CAP.SMPS (support for mechanical presence switches)
PI (ports implemented)
...
Thus, it is a spec violation to not initialize these registers correctly.
If you have an AHCI controller that is a PCIe card, it will surely expose
the Ports Implemented register correctly.
So this problem should be limited to on-boad AHCI controllers, where
BIOS/platform firmware has violated the specification, and not initialized
the registers required by the specification.
If you can disable the controller via BIOS, then the most logical thing is
for these on-boards AHCI controllers to actually get disabled and not show
up in lspci at all. As far as I know, that is how it works on Intel and
AMD systems.
2) Why limit this to AHCI controllers connected via PCI?
Surely device tree platforms with a AHCI controller connected directly to
the AXI bus can also violate the specification and not initialize the PI
(Ports Implemented) register.
Sure, for AHCI controllers connected without a PCI bus, there is the
"ports-implemented" device tree property, that can be used to initialize
this register correctly for platforms without any embedded firmware.
But if we should implement a workaround, why add code that is explcitly
limited to PCI?
I think a better solution is to limit Ports Implemented to CAP.NP.
As Ports Implemented is a bitmask with up to CAP.NP bits.
Ports Implemented is not allowed to be larger than CAP.NP, it can
only mark ports within the CAP.NP MMIO region as "unimplemented".
So the highest bit set in Ports Implemented can not be higher than CAP.NP.
(I.e. a per port MMIO offset is not allowed to be larger than
CAP.NP * MMIO registers per port.)
Looking at the libata code, it looks like we have code for this already:
https://github.com/torvalds/linux/blob/v7.0/drivers/ata/libahci.c#L561-L569
Could you perhaps debug to see why this code in no longer working as
intended?
If you revert 18ee7c49f75b ("ata: ahci: Introduce firmware-specific caps
initialization"), do you see the
"implemented port map (0x%lx) contains more ports than nr_ports (%u), using nr_ports"
warning in the log?
Please share the equivalent to these prints after reverting the commit:
[1825468.337982] ahci 0000:00:17.0: AHCI vers 0001.0301, 32 command slots, 6 Gbps, SATA mode
[1825468.350108] ahci 0000:00:17.0: 8/8 ports implemented (port mask 0xff)
> When mmio_size is 0x1000 and all ports need to be accessed.
> The issue manifests as a panic in __raw_readl() when i=30, as the
> calculated offset (0x1000) equals the typical MMIO region size (0x1000),
> placing the access exactly at the boundary of the mapped region.
AFAICT, port_map gets limited to CAP.NP, and we only access ports that are
within port_map (and this offset < CAP.NP):
for_each_set_bit(i, &port_map, AHCI_MAX_PORTS) {
if (hpriv->saved_port_cap[i])
continue;
port_mmio = __ahci_port_base(hpriv, i);
Wait a second:
> 3. The actual MMIO region is smaller than needed for the indicated
> ports.
I hope you are not saying that your PCI device is only exposing a BAR
that is smaller than needed to contain the number of ports defined in
CAP.NP in this controller?
That sounds like a seriously buggy controller...
Could you please share the output of (with the PCI BDF of you controller
of course):
$ sudo lspci -vvvnns 0000:00:17.0
00:17.0 SATA controller [0106]: Intel Corporation C620 Series Chipset Family SATA Controller [AHCI mode] [8086:a182] (rev 0a) (prog-if 01 [AHCI 1.0])
Subsystem: Super Micro Computer Inc Device [15d9:1b62]
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr+ Stepping- SERR+ FastB2B- DisINTx+
Status: Cap+ 66MHz+ UDF- FastB2B+ ParErr- DEVSEL=medium >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Latency: 0
Interrupt: pin A routed to IRQ 138
NUMA node: 0
IOMMU group: 28
Region 0: Memory at a5984000 (32-bit, non-prefetchable) [size=8K]
Region 1: Memory at a598a000 (32-bit, non-prefetchable) [size=256]
Region 2: I/O ports at 4050 [size=8]
Region 3: I/O ports at 4040 [size=4]
Region 4: I/O ports at 4000 [size=32]
Region 5: Memory at a5700000 (32-bit, non-prefetchable) [size=512K]
So we can see PCI vendor ID, PCI device ID, and size of the BARs?
From 3.3 Port Registers (one set per port):
Port offset = 100h + (PI Asserted Bit Position * 80h)
CAP.NP can represent at most 32 ports. (Note that the register field itself
is 0's based, so a value of 0 means 1 port and a value of 31 means 32 ports.)
If you really have 32 ports, then you would need a BAR that has size:
0x100+(31*0x80) + 0x80 (start offset + size of the per port register itself): 0x1100
Then we would need to add a quirk for your PCI device and PCI vendor ID,
which limits the number of ports to 30 (by setting CAP.NP to 29) for your
AHCI controller.
Kind regards,
Niklas
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Re: [PATCH] ata: libahci: fix panic when accessing ports beyond MMIO region
[not found] ` <55809835.8838.19db9be1205.Coremail.dayou5941@163.com>
@ 2026-04-23 17:19 ` Niklas Cassel
2026-04-24 2:43 ` Damien Le Moal
0 siblings, 1 reply; 4+ messages in thread
From: Niklas Cassel @ 2026-04-23 17:19 UTC (permalink / raw)
To: 李佑鸿; +Cc: dlemoal, linux-ide, linux-kernel, liyouhong
Hello liyouhong,
On Thu, Apr 23, 2026 at 05:48:54PM +0800, 李佑鸿 wrote:
> However, I have already confirmed with the BIOS supplier that when the
> BIOS disables all SATA ports, it does indeed initialize the values of
> the HOST_CAP and HOSTPorts_IMPL registers in accordance with the specifications.
>
>
> /* Register values after disabling SATA in BIOS */
> HOST_CAP (0x00) = 0xffffffff
> HOST_PORTS_IMPL (0x0c) = 0xffffffff
> HOST_VERSION (0x10) = 0xffffffff
> MMIO_SIZE = 4096
I am actually very surprised to see e.g. CAP (0x00) and AHCI VERSION (0x10)
being uninitialized.
These registers are not mentioned in:
AHCI 1.3.1 - 10.1.1 Firmware Specific Initialization
And I would have expected them to have fixed value regardless if BIOS
has enabled the controller or not, because e.g. CAP.NP (number of ports)
and the AHCI version must obviously be known when synthesizing the IP.
> lspci -vvvnns 05:00.0
> 05:00.0 SATA controller [0106]: Phytium Technology Co., Ltd. Device [1db7:d001] (rev 01) (prog-if 01 [AHCI 1.0])
> Region 5: Memory at 5b800000 (32-bit, non-prefetchable) [size=4K]
So the BAR size is 4K (when the controller is disabled).
Could you please enable the controller in BIOS, and then dump the value of
the CAP (0x00) register.
And also double check that the BAR size is still 4K when the controller is
enabled?
We could then add a quirk to drivers/ata/ahci.c, something like:
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 1d73a53370cf..e7250781e9b7 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -2003,6 +2003,10 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (pdev->vendor == 0x177d && pdev->device == 0xa01c)
hpriv->irq_handler = ahci_thunderx_irq_handler;
+
+ /* Phytium SATA controller has empty CAP register */
+ if (pdev->vendor == 0x1db7 && pdev->device == 0xd001)
+ hpriv->saved_cap = 0xC734FF02;
#endif
/* save initial config */
Where you replace 0xC734FF02 with whatever you get when reading the
CAP (0x00) register when the controller is enabled.
Kind regards,
Niklas
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] ata: libahci: fix panic when accessing ports beyond MMIO region
2026-04-23 17:19 ` Niklas Cassel
@ 2026-04-24 2:43 ` Damien Le Moal
0 siblings, 0 replies; 4+ messages in thread
From: Damien Le Moal @ 2026-04-24 2:43 UTC (permalink / raw)
To: Niklas Cassel, 李佑鸿
Cc: linux-ide, linux-kernel, liyouhong
On 4/24/26 02:19, Niklas Cassel wrote:
> Hello liyouhong,
>
> On Thu, Apr 23, 2026 at 05:48:54PM +0800, 李佑鸿 wrote:
>> However, I have already confirmed with the BIOS supplier that when the
>> BIOS disables all SATA ports, it does indeed initialize the values of
>> the HOST_CAP and HOSTPorts_IMPL registers in accordance with the specifications.
>>
>>
>> /* Register values after disabling SATA in BIOS */
>> HOST_CAP (0x00) = 0xffffffff
>> HOST_PORTS_IMPL (0x0c) = 0xffffffff
>> HOST_VERSION (0x10) = 0xffffffff
>> MMIO_SIZE = 4096
>
> I am actually very surprised to see e.g. CAP (0x00) and AHCI VERSION (0x10)
> being uninitialized.
What I am surprised of here is that we even see that device on the PCI bus at
all when it is disabled in the BIOs. If that device is disabled, why are we even
seeing it by scanning the PCI ports ? The adapter should simply not be visible
at all.
Trying to debug register values when we should not even be seeing the device in
the first place does not make sense to me. If anything, I would take a really
big hammer here and try to completely ignore that adapter if we can somehow
detect that it has been in fact disabled in the BIOS. But that detection may be
challenging to do since it seems we are dealing with a very buggy BIOS.
So maybe we should simply warn and exit probe early if we see a PCI BAR size
that is broken.
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-24 2:43 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-22 8:03 [PATCH] ata: libahci: fix panic when accessing ports beyond MMIO region dayou5941
2026-04-22 14:36 ` Niklas Cassel
[not found] ` <55809835.8838.19db9be1205.Coremail.dayou5941@163.com>
2026-04-23 17:19 ` Niklas Cassel
2026-04-24 2:43 ` Damien Le Moal
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox