/* * Copyright (c) 2022, Liav A. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include namespace Kernel { #if ARCH(I386) || ARCH(X86_64) ErrorOr> IOWindow::create_for_io_space(IOAddress address, u64 space_length) { VERIFY(!Checked::addition_would_overflow(address.get(), space_length)); auto io_address_range = TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOAddressData(address.get(), space_length))); return TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOWindow(move(io_address_range)))); } IOWindow::IOWindow(NonnullOwnPtr io_range) : m_space_type(SpaceType::IO) , m_io_range(move(io_range)) { } #endif ErrorOr> IOWindow::create_from_io_window_with_offset(u64 offset, u64 space_length) { #if ARCH(I386) || ARCH(X86_64) if (m_space_type == SpaceType::IO) { VERIFY(m_io_range); if (Checked::addition_would_overflow(m_io_range->address(), space_length)) return Error::from_errno(EOVERFLOW); auto io_address_range = TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOAddressData(as_io_address().offset(offset).get(), space_length))); return TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOWindow(move(io_address_range)))); } #endif VERIFY(space_type() == SpaceType::Memory); VERIFY(m_memory_mapped_range); // Note: x86-IA32 is the only 32 bit CPU architecture currently being supported and // probably will be the only such in the foreseeable future. #if ARCH(I386) if (Checked::addition_would_overflow(m_memory_mapped_range->paddr.get(), offset)) return Error::from_errno(EOVERFLOW); if (Checked::addition_would_overflow(m_memory_mapped_range->paddr.get() + offset, space_length)) return Error::from_errno(EOVERFLOW); #else if (Checked::addition_would_overflow(m_memory_mapped_range->paddr.get(), offset)) return Error::from_errno(EOVERFLOW); if (Checked::addition_would_overflow(m_memory_mapped_range->paddr.get() + offset, space_length)) return Error::from_errno(EOVERFLOW); #endif auto memory_mapped_range = TRY(Memory::adopt_new_nonnull_own_typed_mapping(m_memory_mapped_range->paddr.offset(offset), space_length, Memory::Region::Access::ReadWrite)); return TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOWindow(move(memory_mapped_range)))); } ErrorOr> IOWindow::create_from_io_window_with_offset(u64 offset) { #if ARCH(I386) || ARCH(X86_64) if (m_space_type == SpaceType::IO) { VERIFY(m_io_range); VERIFY(m_io_range->space_length() >= offset); return create_from_io_window_with_offset(offset, m_io_range->space_length() - offset); } #endif VERIFY(space_type() == SpaceType::Memory); VERIFY(m_memory_mapped_range); VERIFY(m_memory_mapped_range->length >= offset); return create_from_io_window_with_offset(offset, m_memory_mapped_range->length - offset); } ErrorOr> IOWindow::create_for_pci_device_bar(PCI::Address const& pci_address, PCI::HeaderType0BaseRegister pci_bar, u64 space_length) { u64 pci_bar_value = PCI::get_BAR(pci_address, pci_bar); auto pci_bar_space_type = PCI::get_BAR_space_type(pci_bar_value); if (pci_bar_space_type == PCI::BARSpaceType::Memory64BitSpace) { // FIXME: In theory, BAR5 cannot be assigned to 64 bit as it is the last one... // however, there might be 64 bit BAR5 for real bare metal hardware, so remove this // if it makes a problem. if (pci_bar == PCI::HeaderType0BaseRegister::BAR5) { return Error::from_errno(EINVAL); } u64 next_pci_bar_value = PCI::get_BAR(pci_address, static_cast(to_underlying(pci_bar) + 1)); pci_bar_value |= next_pci_bar_value << 32; } auto pci_bar_space_size = PCI::get_BAR_space_size(pci_address, pci_bar); if (pci_bar_space_size < space_length) return Error::from_errno(EIO); if (pci_bar_space_type == PCI::BARSpaceType::IOSpace) { #if ARCH(I386) || ARCH(X86_64) if (Checked::addition_would_overflow(pci_bar_value, space_length)) return Error::from_errno(EOVERFLOW); auto io_address_range = TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOAddressData((pci_bar_value & 0xfffffffc), space_length))); return TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOWindow(move(io_address_range)))); #else // Note: For non-x86 platforms, IO PCI BARs are simply not useable. return Error::from_errno(ENOTSUP); #endif } if (pci_bar_space_type == PCI::BARSpaceType::Memory32BitSpace && Checked::addition_would_overflow(pci_bar_value, space_length)) return Error::from_errno(EOVERFLOW); if (pci_bar_space_type == PCI::BARSpaceType::Memory16BitSpace && Checked::addition_would_overflow(pci_bar_value, space_length)) return Error::from_errno(EOVERFLOW); if (pci_bar_space_type == PCI::BARSpaceType::Memory64BitSpace && Checked::addition_would_overflow(pci_bar_value, space_length)) return Error::from_errno(EOVERFLOW); auto memory_mapped_range = TRY(Memory::adopt_new_nonnull_own_typed_mapping(PhysicalAddress(pci_bar_value & 0xfffffff0), space_length, Memory::Region::Access::ReadWrite)); return TRY(adopt_nonnull_own_or_enomem(new (nothrow) IOWindow(move(memory_mapped_range)))); } ErrorOr> IOWindow::create_for_pci_device_bar(PCI::Address const& pci_address, PCI::HeaderType0BaseRegister pci_bar) { u64 pci_bar_space_size = PCI::get_BAR_space_size(pci_address, pci_bar); return create_for_pci_device_bar(pci_address, pci_bar, pci_bar_space_size); } ErrorOr> IOWindow::create_for_pci_device_bar(PCI::DeviceIdentifier const& pci_device_identifier, PCI::HeaderType0BaseRegister pci_bar) { u64 pci_bar_space_size = PCI::get_BAR_space_size(pci_device_identifier.address(), pci_bar); return create_for_pci_device_bar(pci_device_identifier.address(), pci_bar, pci_bar_space_size); } ErrorOr> IOWindow::create_for_pci_device_bar(PCI::DeviceIdentifier const& pci_device_identifier, PCI::HeaderType0BaseRegister pci_bar, u64 space_length) { return create_for_pci_device_bar(pci_device_identifier.address(), pci_bar, space_length); } IOWindow::IOWindow(NonnullOwnPtr> memory_mapped_range) : m_space_type(SpaceType::Memory) , m_memory_mapped_range(move(memory_mapped_range)) { } IOWindow::~IOWindow() = default; bool IOWindow::is_access_aligned(u64 offset, size_t byte_size_access) const { return (offset % byte_size_access) == 0; } bool IOWindow::is_access_in_range(u64 offset, size_t byte_size_access) const { if (Checked::addition_would_overflow(offset, byte_size_access)) return false; #if ARCH(I386) || ARCH(X86_64) if (m_space_type == SpaceType::IO) { VERIFY(m_io_range); VERIFY(!Checked::addition_would_overflow(m_io_range->address(), m_io_range->space_length())); // To understand how we treat IO address space with the corresponding calculation, the Intel Software Developer manual // helps us to understand the layout of the IO address space - // // Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture, 16.3 I/O ADDRESS SPACE, page 16-1 wrote: // Any two consecutive 8-bit ports can be treated as a 16-bit port, and any four consecutive ports can be a 32-bit port. // In this manner, the processor can transfer 8, 16, or 32 bits to or from a device in the I/O address space. // Like words in memory, 16-bit ports should be aligned to even addresses (0, 2, 4, ...) so that all 16 bits can be transferred in a single bus cycle. // Likewise, 32-bit ports should be aligned to addresses that are multiples of four (0, 4, 8, ...). // The processor supports data transfers to unaligned ports, but there is a performance penalty because one or more // extra bus cycle must be used. return (m_io_range->address() + m_io_range->space_length()) >= (offset + byte_size_access); } #endif VERIFY(space_type() == SpaceType::Memory); VERIFY(m_memory_mapped_range); VERIFY(!Checked::addition_would_overflow(m_memory_mapped_range->offset, m_memory_mapped_range->length)); return (m_memory_mapped_range->offset + m_memory_mapped_range->length) >= (offset + byte_size_access); } u8 IOWindow::read8(u64 offset) { VERIFY(is_access_in_range(offset, sizeof(u8))); u8 data { 0 }; in(offset, data); return data; } u16 IOWindow::read16(u64 offset) { // Note: Although it might be OK to allow unaligned access on regular memory, // for memory mapped IO access, it should always be considered a bug. // The same goes for port mapped IO access, because in x86 unaligned access to ports // is possible but there's a performance penalty. VERIFY(is_access_in_range(offset, sizeof(u16))); VERIFY(is_access_aligned(offset, sizeof(u16))); u16 data { 0 }; in(offset, data); return data; } u32 IOWindow::read32(u64 offset) { // Note: Although it might be OK to allow unaligned access on regular memory, // for memory mapped IO access, it should always be considered a bug. // The same goes for port mapped IO access, because in x86 unaligned access to ports // is possible but there's a performance penalty. VERIFY(is_access_in_range(offset, sizeof(u32))); VERIFY(is_access_aligned(offset, sizeof(u32))); u32 data { 0 }; in(offset, data); return data; } void IOWindow::write8(u64 offset, u8 data) { VERIFY(is_access_in_range(offset, sizeof(u8))); out(offset, data); } void IOWindow::write16(u64 offset, u16 data) { // Note: Although it might be OK to allow unaligned access on regular memory, // for memory mapped IO access, it should always be considered a bug. // The same goes for port mapped IO access, because in x86 unaligned access to ports // is possible but there's a performance penalty. VERIFY(is_access_in_range(offset, sizeof(u16))); VERIFY(is_access_aligned(offset, sizeof(u16))); out(offset, data); } void IOWindow::write32(u64 offset, u32 data) { // Note: Although it might be OK to allow unaligned access on regular memory, // for memory mapped IO access, it should always be considered a bug. // The same goes for port mapped IO access, because in x86 unaligned access to ports // is possible but there's a performance penalty. VERIFY(is_access_in_range(offset, sizeof(u32))); VERIFY(is_access_aligned(offset, sizeof(u32))); out(offset, data); } void IOWindow::write32_unaligned(u64 offset, u32 data) { // Note: We only verify that we access IO in the expected range. // Note: for port mapped IO access, because in x86 unaligned access to ports // is possible but there's a performance penalty, we can still allow that to happen. // However, it should be noted that most cases should not use unaligned access // to hardware IO, so this is a valid case in emulators or hypervisors only. // Note: Using this for memory mapped IO will fail for unaligned access, because // there's no valid use case for it (yet). VERIFY(space_type() != SpaceType::Memory); VERIFY(is_access_in_range(offset, sizeof(u32))); out(offset, data); } u32 IOWindow::read32_unaligned(u64 offset) { // Note: We only verify that we access IO in the expected range. // Note: for port mapped IO access, because in x86 unaligned access to ports // is possible but there's a performance penalty, we can still allow that to happen. // However, it should be noted that most cases should not use unaligned access // to hardware IO, so this is a valid case in emulators or hypervisors only. // Note: Using this for memory mapped IO will fail for unaligned access, because // there's no valid use case for it (yet). VERIFY(space_type() != SpaceType::Memory); VERIFY(is_access_in_range(offset, sizeof(u32))); u32 data { 0 }; in(offset, data); return data; } PhysicalAddress IOWindow::as_physical_memory_address() const { VERIFY(space_type() == SpaceType::Memory); VERIFY(m_memory_mapped_range); return m_memory_mapped_range->paddr; } u8 volatile* IOWindow::as_memory_address_pointer() { VERIFY(space_type() == SpaceType::Memory); VERIFY(m_memory_mapped_range); return m_memory_mapped_range->ptr(); } #if ARCH(I386) || ARCH(X86_64) IOAddress IOWindow::as_io_address() const { VERIFY(space_type() == SpaceType::IO); VERIFY(m_io_range); return IOAddress(m_io_range->address()); } #endif }