2021-12-16 12:07:54 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2021, Pankaj R <pankydev8@gmail.com>
|
2022-02-09 15:33:39 -03:00
|
|
|
* Copyright (c) 2022, the SerenityOS developers.
|
2021-12-16 12:07:54 -03:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2022-09-02 04:49:18 -03:00
|
|
|
#include <AK/Format.h>
|
2021-12-16 12:07:54 -03:00
|
|
|
#include <AK/Types.h>
|
2022-09-02 05:23:32 -03:00
|
|
|
#include <Kernel/Arch/Delay.h>
|
2023-04-29 17:27:33 -03:00
|
|
|
#include <Kernel/Arch/Interrupts.h>
|
2022-05-02 17:46:43 -03:00
|
|
|
#include <Kernel/Arch/SafeMem.h>
|
2023-02-24 15:21:53 -03:00
|
|
|
#include <Kernel/Boot/CommandLine.h>
|
2021-12-16 12:07:54 -03:00
|
|
|
#include <Kernel/Bus/PCI/API.h>
|
|
|
|
|
#include <Kernel/Devices/Device.h>
|
2023-03-18 08:32:12 -03:00
|
|
|
#include <Kernel/Devices/Storage/NVMe/NVMeController.h>
|
|
|
|
|
#include <Kernel/Devices/Storage/StorageManagement.h>
|
2022-08-19 15:53:40 -03:00
|
|
|
#include <Kernel/Library/LockRefPtr.h>
|
2021-12-16 12:07:54 -03:00
|
|
|
#include <Kernel/Sections.h>
|
|
|
|
|
|
|
|
|
|
namespace Kernel {
|
|
|
|
|
|
2023-03-14 05:44:21 -03:00
|
|
|
UNMAP_AFTER_INIT ErrorOr<NonnullRefPtr<NVMeController>> NVMeController::try_initialize(Kernel::PCI::DeviceIdentifier const& device_identifier, bool is_queue_polled)
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
2023-03-14 05:44:21 -03:00
|
|
|
auto controller = TRY(adopt_nonnull_ref_or_enomem(new NVMeController(device_identifier, StorageManagement::generate_relative_nvme_controller_id({}))));
|
2022-01-27 08:14:58 -03:00
|
|
|
TRY(controller->initialize(is_queue_polled));
|
2021-12-16 12:07:54 -03:00
|
|
|
return controller;
|
|
|
|
|
}
|
|
|
|
|
|
Kernel/Storage: Introduce new boot device addressing modes
Before of this patch, we supported two methods to address a boot device:
1. Specifying root=/dev/hdXY, where X is a-z letter which corresponds to
a boot device, and Y as number from 1 to 16, to indicate the partition
number, which can be omitted to instruct the kernel to use a raw device
rather than a partition on a raw device.
2. Specifying root=PARTUUID: with a GUID string of a GUID partition. In
case of existing storage device with GPT partitions, this is most likely
the safest option to ensure booting from persistent storage.
While option 2 is more advanced and reliable, the first option has 2
caveats:
1. The string prefix "/dev/hd" doesn't mean anything beside a convention
on Linux installations, that was taken into use in Serenity. In Serenity
we don't mount DevTmpFS before we mount the boot device on /, so the
kernel doesn't really access /dev anyway, so this convention is only a
big misleading relic that can easily make the user to assume we access
/dev early on boot.
2. This convention although resemble the simple linux convention, is
quite limited in specifying a correct boot device across hardware setup
changes, so option 2 was recommended to ensure the system is always
bootable.
With these caveats in mind, this commit tries to fix the problem with
adding more addressing options as well as to remove the first option
being mentioned above of addressing.
To sum it up, there are 4 addressing options:
1. Hardware relative address - Each instance of StorageController is
assigned with a index number relative to the type of hardware it handles
which makes it possible to address storage devices with a prefix of the
commandset ("ata" for ATA, "nvme" for NVMe, "ramdisk" for Plain memory),
and then the number for the parent controller relative hardware index,
another number LUN target_id, and a third number for LUN disk_id.
2. LUN address - Similar to the previous option, but instead we rely on
the parent controller absolute index for the first number.
3. Block device major and minor numbers - by specifying the major and
minor numbers, the kernel can simply try to get the corresponding block
device and use it as the boot device.
4. GUID string, in the same fashion like before, so the user use the
"PARTUUID:" string prefix and add the GUID of the GPT partition.
For the new address modes 1 and 2, the user can choose to also specify a
partition out of the selected boot device. To do that, the user needs to
append the semicolon character and then add the string "partX" where X
is to be changed for the partition number. We start counting from 0, and
therefore the first partition number is 0 and not 1 in the kernel boot
argument.
2022-08-05 14:32:26 -03:00
|
|
|
UNMAP_AFTER_INIT NVMeController::NVMeController(const PCI::DeviceIdentifier& device_identifier, u32 hardware_relative_controller_id)
|
Kernel/PCI: Hold a reference to DeviceIdentifier in the Device class
There are now 2 separate classes for almost the same object type:
- EnumerableDeviceIdentifier, which is used in the enumeration code for
all PCI host controller classes. This is allowed to be moved and
copied, as it doesn't support ref-counting.
- DeviceIdentifier, which inherits from EnumerableDeviceIdentifier. This
class uses ref-counting, and is not allowed to be copied. It has a
spinlock member in its structure to allow safely executing complicated
IO sequences on a PCI device and its space configuration.
There's a static method that allows a quick conversion from
EnumerableDeviceIdentifier to DeviceIdentifier while creating a
NonnullRefPtr out of it.
The reason for doing this is for the sake of integrity and reliablity of
the system in 2 places:
- Ensure that "complicated" tasks that rely on manipulating PCI device
registers are done in a safe manner. For example, determining a PCI
BAR space size requires multiple read and writes to the same register,
and if another CPU tries to do something else with our selected
register, then the result will be a catastrophe.
- Allow the PCI API to have a united form around a shared object which
actually holds much more data than the PCI::Address structure. This is
fundamental if we want to do certain types of optimizations, and be
able to support more features of the PCI bus in the foreseeable
future.
This patch already has several implications:
- All PCI::Device(s) hold a reference to a DeviceIdentifier structure
being given originally from the PCI::Access singleton. This means that
all instances of DeviceIdentifier structures are located in one place,
and all references are pointing to that location. This ensures that
locking the operation spinlock will take effect in all the appropriate
places.
- We no longer support adding PCI host controllers and then immediately
allow for enumerating it with a lambda function. It was found that
this method is extremely broken and too much complicated to work
reliably with the new paradigm being introduced in this patch. This
means that for Volume Management Devices (Intel VMD devices), we
simply first enumerate the PCI bus for such devices in the storage
code, and if we find a device, we attach it in the PCI::Access method
which will scan for devices behind that bridge and will add new
DeviceIdentifier(s) objects to its internal Vector. Afterwards, we
just continue as usual with scanning for actual storage controllers,
so we will find a corresponding NVMe controllers if there were any
behind that VMD bridge.
2022-02-10 13:33:13 -03:00
|
|
|
: PCI::Device(const_cast<PCI::DeviceIdentifier&>(device_identifier))
|
Kernel/Storage: Introduce new boot device addressing modes
Before of this patch, we supported two methods to address a boot device:
1. Specifying root=/dev/hdXY, where X is a-z letter which corresponds to
a boot device, and Y as number from 1 to 16, to indicate the partition
number, which can be omitted to instruct the kernel to use a raw device
rather than a partition on a raw device.
2. Specifying root=PARTUUID: with a GUID string of a GUID partition. In
case of existing storage device with GPT partitions, this is most likely
the safest option to ensure booting from persistent storage.
While option 2 is more advanced and reliable, the first option has 2
caveats:
1. The string prefix "/dev/hd" doesn't mean anything beside a convention
on Linux installations, that was taken into use in Serenity. In Serenity
we don't mount DevTmpFS before we mount the boot device on /, so the
kernel doesn't really access /dev anyway, so this convention is only a
big misleading relic that can easily make the user to assume we access
/dev early on boot.
2. This convention although resemble the simple linux convention, is
quite limited in specifying a correct boot device across hardware setup
changes, so option 2 was recommended to ensure the system is always
bootable.
With these caveats in mind, this commit tries to fix the problem with
adding more addressing options as well as to remove the first option
being mentioned above of addressing.
To sum it up, there are 4 addressing options:
1. Hardware relative address - Each instance of StorageController is
assigned with a index number relative to the type of hardware it handles
which makes it possible to address storage devices with a prefix of the
commandset ("ata" for ATA, "nvme" for NVMe, "ramdisk" for Plain memory),
and then the number for the parent controller relative hardware index,
another number LUN target_id, and a third number for LUN disk_id.
2. LUN address - Similar to the previous option, but instead we rely on
the parent controller absolute index for the first number.
3. Block device major and minor numbers - by specifying the major and
minor numbers, the kernel can simply try to get the corresponding block
device and use it as the boot device.
4. GUID string, in the same fashion like before, so the user use the
"PARTUUID:" string prefix and add the GUID of the GPT partition.
For the new address modes 1 and 2, the user can choose to also specify a
partition out of the selected boot device. To do that, the user needs to
append the semicolon character and then add the string "partX" where X
is to be changed for the partition number. We start counting from 0, and
therefore the first partition number is 0 and not 1 in the kernel boot
argument.
2022-08-05 14:32:26 -03:00
|
|
|
, StorageController(hardware_relative_controller_id)
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-27 08:14:58 -03:00
|
|
|
UNMAP_AFTER_INIT ErrorOr<void> NVMeController::initialize(bool is_queue_polled)
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
// Nr of queues = one queue per core
|
|
|
|
|
auto nr_of_queues = Processor::count();
|
2023-04-21 08:50:57 -03:00
|
|
|
auto queue_type = is_queue_polled ? QueueType::Polled : QueueType::IRQ;
|
2021-12-16 12:07:54 -03:00
|
|
|
|
Kernel/PCI: Hold a reference to DeviceIdentifier in the Device class
There are now 2 separate classes for almost the same object type:
- EnumerableDeviceIdentifier, which is used in the enumeration code for
all PCI host controller classes. This is allowed to be moved and
copied, as it doesn't support ref-counting.
- DeviceIdentifier, which inherits from EnumerableDeviceIdentifier. This
class uses ref-counting, and is not allowed to be copied. It has a
spinlock member in its structure to allow safely executing complicated
IO sequences on a PCI device and its space configuration.
There's a static method that allows a quick conversion from
EnumerableDeviceIdentifier to DeviceIdentifier while creating a
NonnullRefPtr out of it.
The reason for doing this is for the sake of integrity and reliablity of
the system in 2 places:
- Ensure that "complicated" tasks that rely on manipulating PCI device
registers are done in a safe manner. For example, determining a PCI
BAR space size requires multiple read and writes to the same register,
and if another CPU tries to do something else with our selected
register, then the result will be a catastrophe.
- Allow the PCI API to have a united form around a shared object which
actually holds much more data than the PCI::Address structure. This is
fundamental if we want to do certain types of optimizations, and be
able to support more features of the PCI bus in the foreseeable
future.
This patch already has several implications:
- All PCI::Device(s) hold a reference to a DeviceIdentifier structure
being given originally from the PCI::Access singleton. This means that
all instances of DeviceIdentifier structures are located in one place,
and all references are pointing to that location. This ensures that
locking the operation spinlock will take effect in all the appropriate
places.
- We no longer support adding PCI host controllers and then immediately
allow for enumerating it with a lambda function. It was found that
this method is extremely broken and too much complicated to work
reliably with the new paradigm being introduced in this patch. This
means that for Volume Management Devices (Intel VMD devices), we
simply first enumerate the PCI bus for such devices in the storage
code, and if we find a device, we attach it in the PCI::Access method
which will scan for devices behind that bridge and will add new
DeviceIdentifier(s) objects to its internal Vector. Afterwards, we
just continue as usual with scanning for actual storage controllers,
so we will find a corresponding NVMe controllers if there were any
behind that VMD bridge.
2022-02-10 13:33:13 -03:00
|
|
|
PCI::enable_memory_space(device_identifier());
|
|
|
|
|
PCI::enable_bus_mastering(device_identifier());
|
2023-04-24 07:32:12 -03:00
|
|
|
m_bar = PCI::get_BAR0(device_identifier()) & PCI::bar_address_mask;
|
2021-12-16 12:07:54 -03:00
|
|
|
static_assert(sizeof(ControllerRegister) == REG_SQ0TDBL_START);
|
2022-01-16 09:17:42 -03:00
|
|
|
static_assert(sizeof(NVMeSubmission) == (1 << SQ_WIDTH));
|
2021-12-16 12:07:54 -03:00
|
|
|
|
|
|
|
|
// Map only until doorbell register for the controller
|
|
|
|
|
// Queues will individually map the doorbell register respectively
|
2022-08-19 15:53:40 -03:00
|
|
|
m_controller_regs = TRY(Memory::map_typed_writable<ControllerRegister volatile>(PhysicalAddress(m_bar)));
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2022-01-15 17:45:50 -03:00
|
|
|
auto caps = m_controller_regs->cap;
|
2023-03-13 12:30:34 -03:00
|
|
|
m_ready_timeout = Duration::from_milliseconds((CAP_TO(caps) + 1) * 500); // CAP.TO is in 500ms units
|
2022-01-15 17:45:50 -03:00
|
|
|
|
2021-12-16 12:07:54 -03:00
|
|
|
calculate_doorbell_stride();
|
2023-04-29 17:27:33 -03:00
|
|
|
// IO queues + 1 admin queue
|
|
|
|
|
m_irq_type = TRY(reserve_irqs(nr_of_queues + 1, true));
|
|
|
|
|
|
2023-04-21 08:50:57 -03:00
|
|
|
TRY(create_admin_queue(queue_type));
|
2021-12-16 12:07:54 -03:00
|
|
|
VERIFY(m_admin_queue_ready == true);
|
|
|
|
|
|
2022-01-15 17:45:50 -03:00
|
|
|
VERIFY(IO_QUEUE_SIZE < MQES(caps));
|
2021-12-16 12:07:54 -03:00
|
|
|
dbgln_if(NVME_DEBUG, "NVMe: IO queue depth is: {}", IO_QUEUE_SIZE);
|
|
|
|
|
|
2023-08-02 07:41:31 -03:00
|
|
|
TRY(identify_and_init_controller());
|
2021-12-16 12:07:54 -03:00
|
|
|
// Create an IO queue per core
|
|
|
|
|
for (u32 cpuid = 0; cpuid < nr_of_queues; ++cpuid) {
|
|
|
|
|
// qid is zero is used for admin queue
|
2023-04-21 08:50:57 -03:00
|
|
|
TRY(create_io_queue(cpuid + 1, queue_type));
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
TRY(identify_and_init_namespaces());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-15 17:45:50 -03:00
|
|
|
bool NVMeController::wait_for_ready(bool expected_ready_bit_value)
|
|
|
|
|
{
|
2022-02-09 15:33:39 -03:00
|
|
|
constexpr size_t one_ms_io_delay = 1000;
|
2022-01-16 01:28:59 -03:00
|
|
|
auto wait_iterations = m_ready_timeout.to_milliseconds();
|
2022-01-15 17:45:50 -03:00
|
|
|
|
|
|
|
|
u32 expected_rdy = expected_ready_bit_value ? 1 : 0;
|
|
|
|
|
while (((m_controller_regs->csts >> CSTS_RDY_BIT) & 0x1) != expected_rdy) {
|
2022-09-02 05:23:32 -03:00
|
|
|
microseconds_delay(one_ms_io_delay);
|
2022-01-15 17:45:50 -03:00
|
|
|
|
|
|
|
|
if (--wait_iterations == 0) {
|
|
|
|
|
if (((m_controller_regs->csts >> CSTS_RDY_BIT) & 0x1) != expected_rdy) {
|
|
|
|
|
dbgln_if(NVME_DEBUG, "NVMEController: CSTS.RDY still not set to {} after {} ms", expected_rdy, m_ready_timeout.to_milliseconds());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
ErrorOr<void> NVMeController::reset_controller()
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
2022-01-15 17:45:50 -03:00
|
|
|
if ((m_controller_regs->cc & (1 << CC_EN_BIT)) != 0) {
|
|
|
|
|
// If the EN bit is already set, we need to wait
|
|
|
|
|
// until the RDY bit is 1, otherwise the behavior is undefined
|
|
|
|
|
if (!wait_for_ready(true))
|
2023-03-14 10:11:31 -03:00
|
|
|
return Error::from_errno(ETIMEDOUT);
|
2022-01-15 17:45:50 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto cc = m_controller_regs->cc;
|
2021-12-16 12:07:54 -03:00
|
|
|
|
|
|
|
|
cc = cc & ~(1 << CC_EN_BIT);
|
|
|
|
|
|
|
|
|
|
m_controller_regs->cc = cc;
|
|
|
|
|
|
|
|
|
|
full_memory_barrier();
|
|
|
|
|
|
2022-01-15 17:45:50 -03:00
|
|
|
// Wait until the RDY bit is cleared
|
|
|
|
|
if (!wait_for_ready(false))
|
2023-03-14 10:11:31 -03:00
|
|
|
return Error::from_errno(ETIMEDOUT);
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
return {};
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
ErrorOr<void> NVMeController::start_controller()
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
2022-01-15 17:45:50 -03:00
|
|
|
if (!(m_controller_regs->cc & (1 << CC_EN_BIT))) {
|
|
|
|
|
// If the EN bit is not already set, we need to wait
|
|
|
|
|
// until the RDY bit is 0, otherwise the behavior is undefined
|
|
|
|
|
if (!wait_for_ready(false))
|
2023-03-14 10:11:31 -03:00
|
|
|
return Error::from_errno(ETIMEDOUT);
|
2022-01-15 17:45:50 -03:00
|
|
|
}
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2022-01-15 17:45:50 -03:00
|
|
|
auto cc = m_controller_regs->cc;
|
2021-12-16 12:07:54 -03:00
|
|
|
|
|
|
|
|
cc = cc | (1 << CC_EN_BIT);
|
|
|
|
|
cc = cc | (CQ_WIDTH << CC_IOCQES_BIT);
|
|
|
|
|
cc = cc | (SQ_WIDTH << CC_IOSQES_BIT);
|
|
|
|
|
|
|
|
|
|
m_controller_regs->cc = cc;
|
|
|
|
|
|
|
|
|
|
full_memory_barrier();
|
2022-01-15 17:45:50 -03:00
|
|
|
|
|
|
|
|
// Wait until the RDY bit is set
|
|
|
|
|
if (!wait_for_ready(true))
|
2023-03-14 10:11:31 -03:00
|
|
|
return Error::from_errno(ETIMEDOUT);
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
return {};
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
|
2022-01-12 07:39:50 -03:00
|
|
|
UNMAP_AFTER_INIT u32 NVMeController::get_admin_q_dept()
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
u32 aqa = m_controller_regs->aqa;
|
|
|
|
|
// Queue depth is 0 based
|
|
|
|
|
u32 q_depth = min(ACQ_SIZE(aqa), ASQ_SIZE(aqa)) + 1;
|
|
|
|
|
dbgln_if(NVME_DEBUG, "NVMe: Admin queue depth is {}", q_depth);
|
|
|
|
|
return q_depth;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 07:39:50 -03:00
|
|
|
UNMAP_AFTER_INIT ErrorOr<void> NVMeController::identify_and_init_namespaces()
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
|
2022-08-24 10:56:26 -03:00
|
|
|
RefPtr<Memory::PhysicalPage> prp_dma_buffer;
|
2021-12-16 12:07:54 -03:00
|
|
|
OwnPtr<Memory::Region> prp_dma_region;
|
2022-01-20 14:47:39 -03:00
|
|
|
auto namespace_data_struct = TRY(ByteBuffer::create_zeroed(NVMe_IDENTIFY_SIZE));
|
2021-12-16 12:07:54 -03:00
|
|
|
u32 active_namespace_list[NVMe_IDENTIFY_SIZE / sizeof(u32)];
|
|
|
|
|
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_page("Identify PRP"sv, Memory::Region::Access::ReadWrite, prp_dma_buffer));
|
2021-12-16 12:07:54 -03:00
|
|
|
prp_dma_region = move(buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the active namespace
|
|
|
|
|
{
|
|
|
|
|
NVMeSubmission sub {};
|
|
|
|
|
u16 status = 0;
|
|
|
|
|
sub.op = OP_ADMIN_IDENTIFY;
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.identify.data_ptr.prp1 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(prp_dma_buffer->paddr().as_ptr()));
|
|
|
|
|
sub.identify.cns = NVMe_CNS_ID_ACTIVE_NS & 0xff;
|
2021-12-16 12:07:54 -03:00
|
|
|
status = submit_admin_command(sub, true);
|
|
|
|
|
if (status) {
|
2022-12-28 19:55:04 -03:00
|
|
|
dmesgln_pci(*this, "Failed to identify active namespace command");
|
2021-12-16 12:07:54 -03:00
|
|
|
return EFAULT;
|
|
|
|
|
}
|
|
|
|
|
if (void* fault_at; !safe_memcpy(active_namespace_list, prp_dma_region->vaddr().as_ptr(), NVMe_IDENTIFY_SIZE, fault_at)) {
|
|
|
|
|
return EFAULT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Get the NAMESPACE attributes
|
|
|
|
|
{
|
|
|
|
|
NVMeSubmission sub {};
|
|
|
|
|
IdentifyNamespace id_ns {};
|
|
|
|
|
u16 status = 0;
|
|
|
|
|
for (auto nsid : active_namespace_list) {
|
|
|
|
|
memset(prp_dma_region->vaddr().as_ptr(), 0, NVMe_IDENTIFY_SIZE);
|
|
|
|
|
// Invalid NS
|
|
|
|
|
if (nsid == 0)
|
|
|
|
|
break;
|
|
|
|
|
sub.op = OP_ADMIN_IDENTIFY;
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.identify.data_ptr.prp1 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(prp_dma_buffer->paddr().as_ptr()));
|
|
|
|
|
sub.identify.cns = NVMe_CNS_ID_NS & 0xff;
|
|
|
|
|
sub.identify.nsid = nsid;
|
2021-12-16 12:07:54 -03:00
|
|
|
status = submit_admin_command(sub, true);
|
|
|
|
|
if (status) {
|
2022-12-28 19:55:04 -03:00
|
|
|
dmesgln_pci(*this, "Failed identify namespace with nsid {}", nsid);
|
2021-12-16 12:07:54 -03:00
|
|
|
return EFAULT;
|
|
|
|
|
}
|
|
|
|
|
static_assert(sizeof(IdentifyNamespace) == NVMe_IDENTIFY_SIZE);
|
|
|
|
|
if (void* fault_at; !safe_memcpy(&id_ns, prp_dma_region->vaddr().as_ptr(), NVMe_IDENTIFY_SIZE, fault_at)) {
|
|
|
|
|
return EFAULT;
|
|
|
|
|
}
|
|
|
|
|
auto val = get_ns_features(id_ns);
|
|
|
|
|
auto block_counts = val.get<0>();
|
|
|
|
|
auto block_size = 1 << val.get<1>();
|
|
|
|
|
|
|
|
|
|
dbgln_if(NVME_DEBUG, "NVMe: Block count is {} and Block size is {}", block_counts, block_size);
|
|
|
|
|
|
Kernel/Storage: Introduce new boot device addressing modes
Before of this patch, we supported two methods to address a boot device:
1. Specifying root=/dev/hdXY, where X is a-z letter which corresponds to
a boot device, and Y as number from 1 to 16, to indicate the partition
number, which can be omitted to instruct the kernel to use a raw device
rather than a partition on a raw device.
2. Specifying root=PARTUUID: with a GUID string of a GUID partition. In
case of existing storage device with GPT partitions, this is most likely
the safest option to ensure booting from persistent storage.
While option 2 is more advanced and reliable, the first option has 2
caveats:
1. The string prefix "/dev/hd" doesn't mean anything beside a convention
on Linux installations, that was taken into use in Serenity. In Serenity
we don't mount DevTmpFS before we mount the boot device on /, so the
kernel doesn't really access /dev anyway, so this convention is only a
big misleading relic that can easily make the user to assume we access
/dev early on boot.
2. This convention although resemble the simple linux convention, is
quite limited in specifying a correct boot device across hardware setup
changes, so option 2 was recommended to ensure the system is always
bootable.
With these caveats in mind, this commit tries to fix the problem with
adding more addressing options as well as to remove the first option
being mentioned above of addressing.
To sum it up, there are 4 addressing options:
1. Hardware relative address - Each instance of StorageController is
assigned with a index number relative to the type of hardware it handles
which makes it possible to address storage devices with a prefix of the
commandset ("ata" for ATA, "nvme" for NVMe, "ramdisk" for Plain memory),
and then the number for the parent controller relative hardware index,
another number LUN target_id, and a third number for LUN disk_id.
2. LUN address - Similar to the previous option, but instead we rely on
the parent controller absolute index for the first number.
3. Block device major and minor numbers - by specifying the major and
minor numbers, the kernel can simply try to get the corresponding block
device and use it as the boot device.
4. GUID string, in the same fashion like before, so the user use the
"PARTUUID:" string prefix and add the GUID of the GPT partition.
For the new address modes 1 and 2, the user can choose to also specify a
partition out of the selected boot device. To do that, the user needs to
append the semicolon character and then add the string "partX" where X
is to be changed for the partition number. We start counting from 0, and
therefore the first partition number is 0 and not 1 in the kernel boot
argument.
2022-08-05 14:32:26 -03:00
|
|
|
m_namespaces.append(TRY(NVMeNameSpace::try_create(*this, m_queues, nsid, block_counts, block_size)));
|
2021-12-16 12:07:54 -03:00
|
|
|
m_device_count++;
|
|
|
|
|
dbgln_if(NVME_DEBUG, "NVMe: Initialized namespace with NSID: {}", nsid);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 07:41:31 -03:00
|
|
|
ErrorOr<void> NVMeController::identify_and_init_controller()
|
|
|
|
|
{
|
|
|
|
|
RefPtr<Memory::PhysicalPage> prp_dma_buffer;
|
|
|
|
|
OwnPtr<Memory::Region> prp_dma_region;
|
|
|
|
|
IdentifyController ctrl {};
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_page("Identify PRP"sv, Memory::Region::Access::ReadWrite, prp_dma_buffer));
|
|
|
|
|
prp_dma_region = move(buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the controller supports shadow doorbell
|
|
|
|
|
{
|
|
|
|
|
NVMeSubmission sub {};
|
|
|
|
|
u16 status = 0;
|
|
|
|
|
sub.op = OP_ADMIN_IDENTIFY;
|
|
|
|
|
sub.identify.data_ptr.prp1 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(prp_dma_buffer->paddr().as_ptr()));
|
|
|
|
|
sub.identify.cns = NVMe_CNS_ID_CTRL & 0xff;
|
|
|
|
|
status = submit_admin_command(sub, true);
|
|
|
|
|
if (status) {
|
|
|
|
|
dmesgln_pci(*this, "Failed to identify active namespace command");
|
|
|
|
|
return EFAULT;
|
|
|
|
|
}
|
|
|
|
|
if (void* fault_at; !safe_memcpy(&ctrl, prp_dma_region->vaddr().as_ptr(), NVMe_IDENTIFY_SIZE, fault_at)) {
|
|
|
|
|
return EFAULT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctrl.oacs & ID_CTRL_SHADOW_DBBUF_MASK) {
|
|
|
|
|
OwnPtr<Memory::Region> dbbuf_dma_region;
|
|
|
|
|
OwnPtr<Memory::Region> eventidx_dma_region;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_page("shadow dbbuf"sv, Memory::Region::Access::ReadWrite, m_dbbuf_shadow_page));
|
|
|
|
|
dbbuf_dma_region = move(buffer);
|
|
|
|
|
memset(dbbuf_dma_region->vaddr().as_ptr(), 0, PAGE_SIZE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_page("eventidx"sv, Memory::Region::Access::ReadWrite, m_dbbuf_eventidx_page));
|
|
|
|
|
eventidx_dma_region = move(buffer);
|
|
|
|
|
memset(eventidx_dma_region->vaddr().as_ptr(), 0, PAGE_SIZE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
NVMeSubmission sub {};
|
|
|
|
|
sub.op = OP_ADMIN_DBBUF_CONFIG;
|
|
|
|
|
sub.dbbuf_cmd.data_ptr.prp1 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(m_dbbuf_shadow_page->paddr().as_ptr()));
|
|
|
|
|
sub.dbbuf_cmd.data_ptr.prp2 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(m_dbbuf_eventidx_page->paddr().as_ptr()));
|
|
|
|
|
|
|
|
|
|
submit_admin_command(sub, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbgln_if(NVME_DEBUG, "Shadow doorbell Enabled!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 07:39:50 -03:00
|
|
|
UNMAP_AFTER_INIT Tuple<u64, u8> NVMeController::get_ns_features(IdentifyNamespace& identify_data_struct)
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
auto flbas = identify_data_struct.flbas & FLBA_SIZE_MASK;
|
|
|
|
|
auto namespace_size = identify_data_struct.nsze;
|
|
|
|
|
auto lba_format = identify_data_struct.lbaf[flbas];
|
|
|
|
|
|
|
|
|
|
auto lba_size = (lba_format & LBA_SIZE_MASK) >> 16;
|
|
|
|
|
return Tuple<u64, u8>(namespace_size, lba_size);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-19 15:53:40 -03:00
|
|
|
LockRefPtr<StorageDevice> NVMeController::device(u32 index) const
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
return m_namespaces.at(index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t NVMeController::devices_count() const
|
|
|
|
|
{
|
|
|
|
|
return m_device_count;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
ErrorOr<void> NVMeController::reset()
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
2023-03-14 10:11:31 -03:00
|
|
|
TRY(reset_controller());
|
|
|
|
|
TRY(start_controller());
|
|
|
|
|
return {};
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
ErrorOr<void> NVMeController::shutdown()
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
2023-03-14 10:11:31 -03:00
|
|
|
return Error::from_errno(ENOTIMPL);
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NVMeController::complete_current_request([[maybe_unused]] AsyncDeviceRequest::RequestResult result)
|
|
|
|
|
{
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 08:50:57 -03:00
|
|
|
UNMAP_AFTER_INIT ErrorOr<void> NVMeController::create_admin_queue(QueueType queue_type)
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
auto qdepth = get_admin_q_dept();
|
|
|
|
|
OwnPtr<Memory::Region> cq_dma_region;
|
2023-03-06 10:17:01 -03:00
|
|
|
Vector<NonnullRefPtr<Memory::PhysicalPage>> cq_dma_pages;
|
2021-12-16 12:07:54 -03:00
|
|
|
OwnPtr<Memory::Region> sq_dma_region;
|
2023-03-06 10:17:01 -03:00
|
|
|
Vector<NonnullRefPtr<Memory::PhysicalPage>> sq_dma_pages;
|
2021-12-16 12:07:54 -03:00
|
|
|
auto cq_size = round_up_to_power_of_two(CQ_SIZE(qdepth), 4096);
|
|
|
|
|
auto sq_size = round_up_to_power_of_two(SQ_SIZE(qdepth), 4096);
|
2023-03-14 10:11:31 -03:00
|
|
|
auto maybe_error = reset_controller();
|
|
|
|
|
if (maybe_error.is_error()) {
|
2022-12-28 19:55:04 -03:00
|
|
|
dmesgln_pci(*this, "Failed to reset the NVMe controller");
|
2023-03-14 10:11:31 -03:00
|
|
|
return maybe_error;
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_pages(cq_size, "Admin CQ queue"sv, Memory::Region::Access::ReadWrite, cq_dma_pages));
|
2021-12-16 12:07:54 -03:00
|
|
|
cq_dma_region = move(buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase bit is important to determine completion, so zero out the space
|
|
|
|
|
// so that we don't get any garbage phase bit value
|
|
|
|
|
memset(cq_dma_region->vaddr().as_ptr(), 0, cq_size);
|
|
|
|
|
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_pages(sq_size, "Admin SQ queue"sv, Memory::Region::Access::ReadWrite, sq_dma_pages));
|
2021-12-16 12:07:54 -03:00
|
|
|
sq_dma_region = move(buffer);
|
|
|
|
|
}
|
2022-08-19 15:53:40 -03:00
|
|
|
auto doorbell_regs = TRY(Memory::map_typed_writable<DoorbellRegister volatile>(PhysicalAddress(m_bar + REG_SQ0TDBL_START)));
|
2023-08-02 07:37:52 -03:00
|
|
|
Doorbell doorbell = {
|
|
|
|
|
.mmio_reg = move(doorbell_regs),
|
2023-08-02 07:41:31 -03:00
|
|
|
.dbbuf_shadow = {},
|
|
|
|
|
.dbbuf_eventidx = {},
|
2023-08-02 07:37:52 -03:00
|
|
|
};
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2023-03-06 10:17:01 -03:00
|
|
|
m_controller_regs->acq = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(cq_dma_pages.first()->paddr().as_ptr()));
|
|
|
|
|
m_controller_regs->asq = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(sq_dma_pages.first()->paddr().as_ptr()));
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2023-04-29 17:27:33 -03:00
|
|
|
auto irq = TRY(allocate_irq(0)); // Admin queue always uses the 0th index when using MSIx
|
|
|
|
|
|
2023-03-14 10:11:31 -03:00
|
|
|
maybe_error = start_controller();
|
|
|
|
|
if (maybe_error.is_error()) {
|
2022-12-28 19:55:04 -03:00
|
|
|
dmesgln_pci(*this, "Failed to restart the NVMe controller");
|
2023-03-14 10:11:31 -03:00
|
|
|
return maybe_error;
|
2021-12-16 12:07:54 -03:00
|
|
|
}
|
|
|
|
|
set_admin_queue_ready_flag();
|
2023-08-02 07:37:52 -03:00
|
|
|
m_admin_queue = TRY(NVMeQueue::try_create(*this, 0, irq, qdepth, move(cq_dma_region), move(sq_dma_region), move(doorbell), queue_type));
|
2022-01-27 08:14:58 -03:00
|
|
|
|
2021-12-16 12:07:54 -03:00
|
|
|
dbgln_if(NVME_DEBUG, "NVMe: Admin queue created");
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 08:50:57 -03:00
|
|
|
UNMAP_AFTER_INIT ErrorOr<void> NVMeController::create_io_queue(u8 qid, QueueType queue_type)
|
2021-12-16 12:07:54 -03:00
|
|
|
{
|
|
|
|
|
OwnPtr<Memory::Region> cq_dma_region;
|
2023-03-06 10:17:01 -03:00
|
|
|
Vector<NonnullRefPtr<Memory::PhysicalPage>> cq_dma_pages;
|
2021-12-16 12:07:54 -03:00
|
|
|
OwnPtr<Memory::Region> sq_dma_region;
|
2023-03-06 10:17:01 -03:00
|
|
|
Vector<NonnullRefPtr<Memory::PhysicalPage>> sq_dma_pages;
|
2021-12-16 12:07:54 -03:00
|
|
|
auto cq_size = round_up_to_power_of_two(CQ_SIZE(IO_QUEUE_SIZE), 4096);
|
|
|
|
|
auto sq_size = round_up_to_power_of_two(SQ_SIZE(IO_QUEUE_SIZE), 4096);
|
|
|
|
|
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_pages(cq_size, "IO CQ queue"sv, Memory::Region::Access::ReadWrite, cq_dma_pages));
|
2021-12-16 12:07:54 -03:00
|
|
|
cq_dma_region = move(buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase bit is important to determine completion, so zero out the space
|
|
|
|
|
// so that we don't get any garbage phase bit value
|
|
|
|
|
memset(cq_dma_region->vaddr().as_ptr(), 0, cq_size);
|
|
|
|
|
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto buffer = TRY(MM.allocate_dma_buffer_pages(sq_size, "IO SQ queue"sv, Memory::Region::Access::ReadWrite, sq_dma_pages));
|
2021-12-16 12:07:54 -03:00
|
|
|
sq_dma_region = move(buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2022-01-16 09:17:42 -03:00
|
|
|
NVMeSubmission sub {};
|
2021-12-16 12:07:54 -03:00
|
|
|
sub.op = OP_ADMIN_CREATE_COMPLETION_QUEUE;
|
2023-03-06 10:17:01 -03:00
|
|
|
sub.create_cq.prp1 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(cq_dma_pages.first()->paddr().as_ptr()));
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.create_cq.cqid = qid;
|
2021-12-16 12:07:54 -03:00
|
|
|
// The queue size is 0 based
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.create_cq.qsize = AK::convert_between_host_and_little_endian(IO_QUEUE_SIZE - 1);
|
2023-04-21 08:50:57 -03:00
|
|
|
auto flags = (queue_type == QueueType::IRQ) ? QUEUE_IRQ_ENABLED : QUEUE_IRQ_DISABLED;
|
2022-01-27 08:14:58 -03:00
|
|
|
flags |= QUEUE_PHY_CONTIGUOUS;
|
2023-04-29 17:27:33 -03:00
|
|
|
// When using MSIx interrupts, qid is used as an index into the interrupt table
|
|
|
|
|
sub.create_cq.irq_vector = (m_irq_type == PCI::InterruptType::PIN) ? 0 : qid;
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.create_cq.cq_flags = AK::convert_between_host_and_little_endian(flags & 0xFFFF);
|
2021-12-16 12:07:54 -03:00
|
|
|
submit_admin_command(sub, true);
|
|
|
|
|
}
|
|
|
|
|
{
|
2022-01-16 09:17:42 -03:00
|
|
|
NVMeSubmission sub {};
|
2021-12-16 12:07:54 -03:00
|
|
|
sub.op = OP_ADMIN_CREATE_SUBMISSION_QUEUE;
|
2023-03-06 10:17:01 -03:00
|
|
|
sub.create_sq.prp1 = reinterpret_cast<u64>(AK::convert_between_host_and_little_endian(sq_dma_pages.first()->paddr().as_ptr()));
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.create_sq.sqid = qid;
|
2021-12-16 12:07:54 -03:00
|
|
|
// The queue size is 0 based
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.create_sq.qsize = AK::convert_between_host_and_little_endian(IO_QUEUE_SIZE - 1);
|
2022-01-27 08:14:58 -03:00
|
|
|
auto flags = QUEUE_PHY_CONTIGUOUS;
|
2022-01-16 09:17:42 -03:00
|
|
|
sub.create_sq.cqid = qid;
|
|
|
|
|
sub.create_sq.sq_flags = AK::convert_between_host_and_little_endian(flags);
|
2021-12-16 12:07:54 -03:00
|
|
|
submit_admin_command(sub, true);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 07:41:31 -03:00
|
|
|
auto queue_doorbell_offset = (2 * qid) * (4 << m_dbl_stride);
|
|
|
|
|
auto doorbell_regs = TRY(Memory::map_typed_writable<DoorbellRegister volatile>(PhysicalAddress(m_bar + REG_SQ0TDBL_START + queue_doorbell_offset)));
|
|
|
|
|
Memory::TypedMapping<DoorbellRegister> shadow_doorbell_regs {};
|
|
|
|
|
Memory::TypedMapping<DoorbellRegister> eventidx_doorbell_regs {};
|
|
|
|
|
|
|
|
|
|
if (!m_dbbuf_shadow_page.is_null()) {
|
|
|
|
|
shadow_doorbell_regs = TRY(Memory::map_typed_writable<DoorbellRegister>(m_dbbuf_shadow_page->paddr().offset(queue_doorbell_offset)));
|
|
|
|
|
eventidx_doorbell_regs = TRY(Memory::map_typed_writable<DoorbellRegister>(m_dbbuf_eventidx_page->paddr().offset(queue_doorbell_offset)));
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 07:37:52 -03:00
|
|
|
Doorbell doorbell = {
|
|
|
|
|
.mmio_reg = move(doorbell_regs),
|
2023-08-02 07:41:31 -03:00
|
|
|
.dbbuf_shadow = move(shadow_doorbell_regs),
|
|
|
|
|
.dbbuf_eventidx = move(eventidx_doorbell_regs),
|
2023-08-02 07:37:52 -03:00
|
|
|
};
|
|
|
|
|
|
2023-04-29 17:27:33 -03:00
|
|
|
auto irq = TRY(allocate_irq(qid));
|
2021-12-16 12:07:54 -03:00
|
|
|
|
2023-08-02 07:37:52 -03:00
|
|
|
m_queues.append(TRY(NVMeQueue::try_create(*this, qid, irq, IO_QUEUE_SIZE, move(cq_dma_region), move(sq_dma_region), move(doorbell), queue_type)));
|
2021-12-16 12:07:54 -03:00
|
|
|
dbgln_if(NVME_DEBUG, "NVMe: Created IO Queue with QID{}", m_queues.size());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|