ladybird/Kernel/IOWindow.h
Liav A 05ba034000 Kernel: Introduce the IOWindow class
This class is intended to replace all IOAddress usages in the Kernel
codebase altogether. The idea is to ensure IO can be done in
arch-specific manner that is determined mostly in compile-time, but to
still be able to use most of the Kernel code in non-x86 builds. Specific
devices that rely on x86-specific IO instructions are already placed in
the Arch/x86 directory and are omitted for non-x86 builds.

The reason this works so well is the fact that x86 IO space acts in a
similar fashion to the traditional memory space being available in most
CPU architectures - the x86 IO space is essentially just an array of
bytes like the physical memory address space, but requires x86 IO
instructions to load and store data. Therefore, many devices allow host
software to interact with the hardware registers in both ways, with a
noticeable trend even in the modern x86 hardware to move away from the
old x86 IO space to exclusively using memory-mapped IO.

Therefore, the IOWindow class encapsulates both methods for x86 builds.
The idea is to allow PCI devices to be used in either way in x86 builds,
so when trying to map an IOWindow on a PCI BAR, the Kernel will try to
find the proper method being declared with the PCI BAR flags.
For old PCI hardware on non-x86 builds this might turn into a problem as
we can't use port mapped IO, so the Kernel will gracefully fail with
ENOTSUP error code if that's the case, as there's really nothing we can
do within such case.

For general IO, the read{8,16,32} and write{8,16,32} methods are
available as a convenient API for other places in the Kernel. There are
simply no direct 64-bit IO API methods yet, as it's not needed right now
and is not considered to be Arch-agnostic too - the x86 IO space doesn't
support generating 64 bit cycle on IO bus and instead requires two 2
32-bit accesses. If for whatever reason it appears to be necessary to do
IO in such manner, it could probably be added with some neat tricks to
do so. It is recommended to use Memory::TypedMapping struct if direct 64
bit IO is actually needed.
2022-09-23 17:22:15 +01:00

163 lines
5.6 KiB
C++

/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteReader.h>
#include <AK/Platform.h>
#include <AK/Types.h>
#if ARCH(I386) || ARCH(X86_64)
# include <Kernel/Arch/x86/IO.h>
#endif
#include <Kernel/Bus/PCI/Definitions.h>
#include <Kernel/Memory/TypedMapping.h>
#include <Kernel/PhysicalAddress.h>
namespace Kernel {
class IOWindow {
public:
enum class SpaceType {
#if ARCH(I386) || ARCH(X86_64)
IO,
#endif
Memory,
};
SpaceType space_type() const { return m_space_type; }
template<typename V>
void write();
#if ARCH(I386) || ARCH(X86_64)
static ErrorOr<NonnullOwnPtr<IOWindow>> create_for_io_space(IOAddress, u64 space_length);
#endif
static ErrorOr<NonnullOwnPtr<IOWindow>> create_for_pci_device_bar(PCI::DeviceIdentifier const&, PCI::HeaderType0BaseRegister, u64 space_length);
static ErrorOr<NonnullOwnPtr<IOWindow>> create_for_pci_device_bar(PCI::DeviceIdentifier const&, PCI::HeaderType0BaseRegister);
static ErrorOr<NonnullOwnPtr<IOWindow>> create_for_pci_device_bar(PCI::Address const&, PCI::HeaderType0BaseRegister, u64 space_length);
static ErrorOr<NonnullOwnPtr<IOWindow>> create_for_pci_device_bar(PCI::Address const&, PCI::HeaderType0BaseRegister);
ErrorOr<NonnullOwnPtr<IOWindow>> create_from_io_window_with_offset(u64 offset, u64 space_length);
ErrorOr<NonnullOwnPtr<IOWindow>> create_from_io_window_with_offset(u64 offset);
bool is_access_valid(u64 offset, size_t byte_size_access) const;
u8 read8(u64 offset);
u16 read16(u64 offset);
u32 read32(u64 offset);
void write8(u64 offset, u8);
void write16(u64 offset, u16);
void write32(u64 offset, u32);
// Note: These methods are useful in exceptional cases where we need to do unaligned
// access. This mostly happens on emulators and hypervisors (such as VMWare) because they don't enforce aligned access
// to IO and sometimes even require such access, so we have to use these functions.
void write32_unaligned(u64 offset, u32);
u32 read32_unaligned(u64 offset);
bool operator==(IOWindow const& other) const = delete;
bool operator!=(IOWindow const& other) const = delete;
bool operator>(IOWindow const& other) const = delete;
bool operator>=(IOWindow const& other) const = delete;
bool operator<(IOWindow const& other) const = delete;
bool operator<=(IOWindow const& other) const = delete;
~IOWindow();
PhysicalAddress as_physical_memory_address() const;
#if ARCH(I386) || ARCH(X86_64)
IOAddress as_io_address() const;
#endif
private:
explicit IOWindow(NonnullOwnPtr<Memory::TypedMapping<volatile u8>>);
u8 volatile* as_memory_address_pointer();
#if ARCH(I386) || ARCH(X86_64)
struct IOAddressData {
public:
IOAddressData(u64 address, u64 space_length)
: m_address(address)
, m_space_length(space_length)
{
}
u64 address() const { return m_address; }
u64 space_length() const { return m_space_length; }
private:
u64 m_address { 0 };
u64 m_space_length { 0 };
};
explicit IOWindow(NonnullOwnPtr<IOAddressData>);
#endif
bool is_access_in_range(u64 offset, size_t byte_size_access) const;
bool is_access_aligned(u64 offset, size_t byte_size_access) const;
template<typename T>
ALWAYS_INLINE void in(u64 start_offset, T& data)
{
#if ARCH(I386) || ARCH(X86_64)
if (m_space_type == SpaceType::IO) {
data = as_io_address().offset(start_offset).in<T>();
return;
}
#endif
VERIFY(m_space_type == SpaceType::Memory);
VERIFY(m_memory_mapped_range);
// Note: For memory-mapped IO we simply never allow unaligned access as it
// can cause problems with strict bare metal hardware. For example, some XHCI USB controllers
// might completely lock up because of an unaligned memory access to their registers.
VERIFY((start_offset % sizeof(T)) == 0);
data = *(volatile T*)(as_memory_address_pointer() + start_offset);
}
template<typename T>
ALWAYS_INLINE void out(u64 start_offset, T value)
{
#if ARCH(I386) || ARCH(X86_64)
if (m_space_type == SpaceType::IO) {
VERIFY(m_io_range);
as_io_address().offset(start_offset).out<T>(value);
return;
}
#endif
VERIFY(m_space_type == SpaceType::Memory);
VERIFY(m_memory_mapped_range);
// Note: For memory-mapped IO we simply never allow unaligned access as it
// can cause problems with strict bare metal hardware. For example, some XHCI USB controllers
// might completely lock up because of an unaligned memory access to their registers.
VERIFY((start_offset % sizeof(T)) == 0);
*(volatile T*)(as_memory_address_pointer() + start_offset) = value;
}
SpaceType m_space_type { SpaceType::Memory };
OwnPtr<Memory::TypedMapping<volatile u8>> m_memory_mapped_range;
#if ARCH(I386) || ARCH(X86_64)
OwnPtr<IOAddressData> m_io_range;
#endif
};
}
template<>
struct AK::Formatter<Kernel::IOWindow> : AK::Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, Kernel::IOWindow const& value)
{
#if ARCH(I386) || ARCH(X86_64)
if (value.space_type() == Kernel::IOWindow::SpaceType::IO)
return Formatter<FormatString>::format(builder, "{}"sv, value.as_io_address());
#endif
VERIFY(value.space_type() == Kernel::IOWindow::SpaceType::Memory);
return Formatter<FormatString>::format(builder, "Memory {}"sv, value.as_physical_memory_address());
}
};