1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-29 20:17:46 +00:00

Kernel: Improve the aarch64 kernel source files disk layout

This commit is contained in:
James Mintram 2022-04-03 21:42:10 +01:00 committed by Linus Groh
parent b884c5746d
commit d94c7fa417
21 changed files with 23 additions and 12 deletions

View file

@ -0,0 +1,114 @@
/*
* Copyright (c) 2021, Marcin Undak <mcinek@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/aarch64/Framebuffer.h>
#include <Kernel/Arch/aarch64/FramebufferMailboxMessages.h>
#include <Kernel/Arch/aarch64/Utils.h>
namespace Prekernel {
Framebuffer::Framebuffer()
{
// FIXME: query HDMI for best mode
// https://github.com/raspberrypi/userland/blob/master/host_applications/linux/apps/tvservice/tvservice.c
m_width = 1280;
m_height = 720;
m_depth = 32;
m_initialized = false;
struct __attribute__((aligned(16))) {
Mailbox::MessageHeader header;
FramebufferSetPhysicalSizeMboxMessage set_physical_size;
FramebufferSetVirtualSizeMboxMessage set_virtual_size;
FramebufferSetVirtualOffsetMboxMessage set_virtual_offset;
FramebufferSetDepthMboxMessage set_depth;
FramebufferSetPixelOrderMboxMessage set_pixel_order;
FramebufferAllocateBufferMboxMessage allocate_buffer;
FramebufferGetPithMboxMessage get_pitch;
Mailbox::MessageTail tail;
} message_queue;
message_queue.header.set_queue_size(sizeof(message_queue));
message_queue.set_physical_size.width = m_width;
message_queue.set_physical_size.height = m_height;
message_queue.set_virtual_size.width = m_width;
message_queue.set_virtual_size.height = m_height;
// FIXME! those next 2 lines crash...
// message_queue.set_virtual_offset.x = 0;
// message_queue.set_virtual_offset.y = 0;
message_queue.set_depth.depth_bits = 32;
message_queue.set_pixel_order.pixel_order = FramebufferSetPixelOrderMboxMessage::PixelOrder::RGB;
message_queue.allocate_buffer.alignment = 4096;
if (!Mailbox::the().send_queue(&message_queue, sizeof(message_queue))) {
warnln("Framebuffer(): Mailbox send failed.");
return;
}
// Now message queue contains responses. Process them.
if (message_queue.set_physical_size.width != m_width || message_queue.set_physical_size.height != m_height) {
warnln("Framebuffer(): Setting physical dimension failed.");
return;
}
if (message_queue.set_virtual_size.width != m_width || message_queue.set_virtual_size.height != m_height) {
warnln("Framebuffer(): Setting virtual dimension failed.");
return;
}
if (message_queue.set_virtual_offset.x != 0 || message_queue.set_virtual_offset.y != 0) {
warnln("Framebuffer(): Setting virtual offset failed.");
return;
}
if (message_queue.set_depth.depth_bits != m_depth) {
warnln("Framebuffer(): Setting depth failed.");
return;
}
if (message_queue.allocate_buffer.size == 0 || message_queue.allocate_buffer.address == 0) {
warnln("Framebuffer(): Allocating buffer failed.");
return;
}
if (message_queue.get_pitch.pitch == 0) {
warnln("Framebuffer(): Retrieving pitch failed.");
return;
}
// Convert GPU address space to RAM
// GPU maps memory from 0x80000000 instead of 0x00000000
m_gpu_buffer = reinterpret_cast<u8*>(message_queue.allocate_buffer.address & 0x3FFFFFFF);
m_buffer_size = message_queue.allocate_buffer.size;
m_pitch = message_queue.get_pitch.pitch;
switch (message_queue.set_pixel_order.pixel_order) {
case FramebufferSetPixelOrderMboxMessage::PixelOrder::RGB:
m_pixel_order = PixelOrder::RGB;
break;
case FramebufferSetPixelOrderMboxMessage::PixelOrder::BGR:
m_pixel_order = PixelOrder::BGR;
break;
default:
warnln("Framebuffer(): Unsupported pixel order reported by GPU.");
m_pixel_order = PixelOrder::RGB;
break;
}
dbgln("Initialized framebuffer: 1280 x 720 @ 32 bits");
m_initialized = true;
}
Framebuffer& Framebuffer::the()
{
static Framebuffer instance;
return instance;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2021, Marcin Undak <mcinek@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Prekernel {
class Framebuffer {
public:
enum class PixelOrder {
RGB,
BGR,
};
static Framebuffer& the();
bool initialized() const { return m_initialized; }
u16 width() const { return m_width; }
u16 height() const { return m_height; }
u8 depth() const { return m_depth; }
u8* gpu_buffer() const { return m_gpu_buffer; }
u32 buffer_size() const { return m_buffer_size; }
u32 pitch() const { return m_pitch; }
PixelOrder pixel_order() { return m_pixel_order; }
private:
u16 m_width;
u16 m_height;
u8 m_depth;
u8* m_gpu_buffer;
u32 m_buffer_size;
u32 m_pitch;
bool m_initialized;
PixelOrder m_pixel_order;
Framebuffer();
};
}

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2021, Marcin Undak <mcinek@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Arch/aarch64/Mailbox.h>
namespace Prekernel {
class FramebufferSetPhysicalSizeMboxMessage : public Mailbox::Message {
public:
u32 width;
u32 height;
FramebufferSetPhysicalSizeMboxMessage()
: Mailbox::Message(0x48003, 8)
{
width = 0;
height = 0;
}
};
static_assert(sizeof(FramebufferSetPhysicalSizeMboxMessage) == 20);
class FramebufferSetVirtualSizeMboxMessage : public Mailbox::Message {
public:
u32 width;
u32 height;
FramebufferSetVirtualSizeMboxMessage()
: Mailbox::Message(0x48004, 8)
{
width = 0;
height = 0;
}
};
static_assert(sizeof(FramebufferSetVirtualSizeMboxMessage) == 20);
class FramebufferSetVirtualOffsetMboxMessage : public Mailbox::Message {
public:
u32 x;
u32 y;
FramebufferSetVirtualOffsetMboxMessage()
: Mailbox::Message(0x48009, 8)
{
x = 0;
y = 0;
}
};
static_assert(sizeof(FramebufferSetVirtualOffsetMboxMessage) == 20);
class FramebufferSetDepthMboxMessage : public Mailbox::Message {
public:
u32 depth_bits;
FramebufferSetDepthMboxMessage()
: Mailbox::Message(0x48005, 4)
{
depth_bits = 0;
}
};
static_assert(sizeof(FramebufferSetDepthMboxMessage) == 16);
class FramebufferSetPixelOrderMboxMessage : public Mailbox::Message {
public:
enum PixelOrder : u32 {
BGR = 0,
RGB = 1
};
PixelOrder pixel_order;
FramebufferSetPixelOrderMboxMessage()
: Mailbox::Message(0x48006, 4)
{
pixel_order = PixelOrder::BGR;
}
};
static_assert(sizeof(FramebufferSetPixelOrderMboxMessage) == 16);
class FramebufferAllocateBufferMboxMessage : public Mailbox::Message {
public:
union {
u32 alignment;
u32 address;
};
u32 size = 0;
FramebufferAllocateBufferMboxMessage()
: Mailbox::Message(0x40001, 8)
{
alignment = 0;
size = 0;
}
};
static_assert(sizeof(FramebufferAllocateBufferMboxMessage) == 20);
class FramebufferGetPithMboxMessage : public Mailbox::Message {
public:
u32 pitch;
FramebufferGetPithMboxMessage()
: Mailbox::Message(0x40008, 4)
{
pitch = 0;
}
};
static_assert(sizeof(FramebufferGetPithMboxMessage) == 16);
}

View file

@ -0,0 +1,98 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/aarch64/GPIO.h>
#include <Kernel/Arch/aarch64/MMIO.h>
extern "C" void wait_cycles(int n);
namespace Prekernel {
// See BCM2835-ARM-Peripherals.pdf section "6 General Purpose I/O" or bcm2711-peripherals.pdf "Chapter 5. General Purpose I/O".
// "6.1 Register View" / "5.2 Register View"
struct PinData {
u32 bits[2];
u32 reserved;
};
struct GPIOControlRegisters {
u32 function_select[6]; // Every u32 stores a 3-bit function code for 10 pins.
u32 reserved;
PinData output_set;
PinData output_clear;
PinData level;
PinData event_detect_status;
PinData rising_edge_detect_enable;
PinData falling_edge_detect_enable;
PinData high_detect_enable;
PinData low_detect_enable;
PinData async_rising_edge_detect_enable;
PinData async_falling_edge_detect_enable;
u32 pull_up_down_enable;
PinData pull_up_down_enable_clock;
u32 test;
};
GPIO::GPIO()
: m_registers(MMIO::the().peripheral<GPIOControlRegisters>(0x20'0000))
{
}
GPIO& GPIO::the()
{
static GPIO instance;
return instance;
}
void GPIO::set_pin_function(unsigned pin_number, PinFunction function)
{
// pin_number must be <= 53. We can't VERIFY() that since this function runs too early to print assertion failures.
unsigned function_select_index = pin_number / 10;
unsigned function_select_bits_start = (pin_number % 10) * 3;
u32 function_bits = m_registers->function_select[function_select_index];
function_bits = (function_bits & ~(0b111 << function_select_bits_start)) | (static_cast<u32>(function) << function_select_bits_start);
m_registers->function_select[function_select_index] = function_bits;
}
void GPIO::internal_enable_pins(u32 enable[2], PullUpDownState state)
{
// Section "GPIO Pull-up/down Clock Registers (GPPUDCLKn)":
// The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on
// the respective GPIO pins. These registers must be used in conjunction with the GPPUD
// register to effect GPIO Pull-up/down changes. The following sequence of events is
// required:
// 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither
// to remove the current Pull-up/down)
m_registers->pull_up_down_enable = static_cast<u32>(state);
// 2. Wait 150 cycles this provides the required set-up time for the control signal
wait_cycles(150);
// 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to
// modify NOTE only the pads which receive a clock will be modified, all others will
// retain their previous state.
m_registers->pull_up_down_enable_clock.bits[0] = enable[0];
m_registers->pull_up_down_enable_clock.bits[1] = enable[1];
// 4. Wait 150 cycles this provides the required hold time for the control signal
wait_cycles(150);
// 5. Write to GPPUD to remove the control signal
m_registers->pull_up_down_enable = 0;
// 6. Write to GPPUDCLK0/1 to remove the clock
m_registers->pull_up_down_enable_clock.bits[0] = 0;
m_registers->pull_up_down_enable_clock.bits[1] = 0;
// bcm2711-peripherals.pdf documents GPIO_PUP_PDN_CNTRL_REG[4] registers that store 2 bits state per register, similar to function_select.
// I don't know if the RPi3 has that already, so this uses the old BCM2835 approach for now.
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Array.h>
#include <AK/Types.h>
namespace Prekernel {
struct GPIOControlRegisters;
// Can configure the general-purpose I/O registers on a Raspberry Pi.
class GPIO {
public:
enum class PinFunction {
Input = 0,
Output = 1,
Alternate0 = 0b100,
Alternate1 = 0b101,
Alternate2 = 0b110,
Alternate3 = 0b111,
Alternate4 = 0b011,
Alternate5 = 0b010,
};
static GPIO& the();
void set_pin_function(unsigned pin_number, PinFunction);
enum class PullUpDownState {
Disable = 0,
PullDown = 1,
PullUp = 2,
};
template<size_t N>
void set_pin_pull_up_down_state(Array<int, N> pins, PullUpDownState state)
{
u32 enable[2] = {};
for (int pin : pins) {
if (pin < 32)
enable[0] |= (1 << pin);
else
enable[1] |= (1 << (pin - 32));
}
internal_enable_pins(enable, state);
}
private:
GPIO();
void internal_enable_pins(u32 enable[2], PullUpDownState state);
GPIOControlRegisters volatile* m_registers;
};
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/aarch64/MMIO.h>
#include <Kernel/Arch/aarch64/MainIdRegister.h>
namespace Prekernel {
MMIO::MMIO()
: m_base_address(0xFE00'0000)
{
MainIdRegister id;
if (id.part_num() <= MainIdRegister::RaspberryPi3)
m_base_address = 0x3F00'0000;
}
MMIO& MMIO::the()
{
static MMIO instance;
return instance;
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Prekernel {
// Knows about memory-mapped IO addresses on the Broadcom family of SOCs used in Raspberry Pis.
// RPi3 is the first Raspberry Pi that supports aarch64.
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf (RPi3)
// https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf (RPi4 Model B)
class MMIO {
public:
static MMIO& the();
u32 read(FlatPtr offset) { return *peripheral_address(offset); }
void write(FlatPtr offset, u32 value) { *peripheral_address(offset) = value; }
u32 volatile* peripheral_address(FlatPtr offset) { return (u32 volatile*)(m_base_address + offset); }
template<class T>
T volatile* peripheral(FlatPtr offset) { return (T volatile*)peripheral_address(offset); }
FlatPtr peripheral_base_address() const { return m_base_address; }
FlatPtr peripheral_end_address() const { return m_base_address + 0x00FFFFFF; }
private:
MMIO();
unsigned int m_base_address;
};
}

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/aarch64/MMIO.h>
#include <Kernel/Arch/aarch64/Mailbox.h>
namespace Prekernel {
// There's one mailbox at MBOX_BASE_OFFSET for reading responses from VideoCore, and one at MBOX_BASE_OFFSET + 0x20 for sending requests.
// Each has its own status word.
constexpr u32 MBOX_BASE_OFFSET = 0xB880;
constexpr u32 MBOX_0 = MBOX_BASE_OFFSET;
constexpr u32 MBOX_1 = MBOX_BASE_OFFSET + 0x20;
constexpr u32 MBOX_READ_DATA = MBOX_0;
constexpr u32 MBOX_READ_POLL = MBOX_0 + 0x10;
constexpr u32 MBOX_READ_SENDER = MBOX_0 + 0x14;
constexpr u32 MBOX_READ_STATUS = MBOX_0 + 0x18;
constexpr u32 MBOX_READ_CONFIG = MBOX_0 + 0x1C;
constexpr u32 MBOX_WRITE_DATA = MBOX_1;
constexpr u32 MBOX_WRITE_STATUS = MBOX_1 + 0x18;
constexpr u32 MBOX_RESPONSE_SUCCESS = 0x8000'0000;
constexpr u32 MBOX_RESPONSE_PARTIAL = 0x8000'0001;
constexpr u32 MBOX_REQUEST = 0;
constexpr u32 MBOX_FULL = 0x8000'0000;
constexpr u32 MBOX_EMPTY = 0x4000'0000;
constexpr int ARM_TO_VIDEOCORE_CHANNEL = 8;
Mailbox::Message::Message(u32 tag, u32 arguments_size)
{
m_tag = tag;
m_arguments_size = arguments_size;
m_command_tag = MBOX_REQUEST;
}
Mailbox::MessageHeader::MessageHeader()
{
m_message_queue_size = 0;
m_command_tag = MBOX_REQUEST;
}
bool Mailbox::MessageHeader::success() const
{
return m_command_tag == MBOX_RESPONSE_SUCCESS;
}
Mailbox& Mailbox::the()
{
static Mailbox instance;
return instance;
}
static void wait_until_we_can_write(MMIO& mmio)
{
// Since nothing else writes to the mailbox, this wait is mostly cargo-culted.
// Most baremetal tutorials on the internet query MBOX_READ_STATUS here, which I think is incorrect and only works because this wait really isn't needed.
while (mmio.read(MBOX_WRITE_STATUS) & MBOX_FULL)
;
}
static void wait_for_reply(MMIO& mmio)
{
while (mmio.read(MBOX_READ_STATUS) & MBOX_EMPTY)
;
}
bool Mailbox::send_queue(void* queue, u32 queue_size) const
{
// According to Raspberry Pi specs this is the only channel implemented.
const u32 channel = ARM_TO_VIDEOCORE_CHANNEL;
auto message_header = reinterpret_cast<MessageHeader*>(queue);
message_header->set_queue_size(queue_size);
auto& mmio = MMIO::the();
// The mailbox interface has a FIFO for message delivery in both directions.
// Responses can be delivered out of order to requests, but we currently ever only send on request at once.
// It'd be nice to have an async interface here where we send a message, then return immediately, and read the response when an interrupt arrives.
// But for now, this is synchronous.
wait_until_we_can_write(mmio);
// The mailbox message is 32-bit based, so this assumes that message is in the first 4 GiB.
u32 request = static_cast<u32>(reinterpret_cast<FlatPtr>(queue) & ~0xF) | (channel & 0xF);
mmio.write(MBOX_WRITE_DATA, request);
for (;;) {
wait_for_reply(mmio);
u32 response = mmio.read(MBOX_READ_DATA);
// We keep at most one message in flight and do synchronous communication, so response will always be == request for us.
if (response == request)
return message_header->success();
}
return true;
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Prekernel {
// Can exchange mailbox messages with the Raspberry Pi's VideoCore chip.
// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
class Mailbox {
public:
// Base class for Mailbox messages. Implemented in subsystems that use Mailbox.
class Message {
protected:
Message(u32 tag, u32 arguments_size);
private:
u32 m_tag;
u32 m_arguments_size;
u32 m_command_tag;
};
// Must be at the beginning of every command message queue
class MessageHeader {
public:
MessageHeader();
u32 queue_size() { return m_message_queue_size; }
void set_queue_size(u32 size) { m_message_queue_size = size; }
bool success() const;
private:
u32 m_message_queue_size;
u32 m_command_tag;
};
// Must be at the end of every command message queue
class MessageTail {
private:
u32 m_empty_tag = 0;
};
static Mailbox& the();
// Sends message queue to VideoCore
bool send_queue(void* queue, u32 queue_size) const;
};
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/aarch64/MMIO.h>
#include <Kernel/Arch/aarch64/Mailbox.h>
#include <Kernel/Arch/aarch64/Timer.h>
#include <Kernel/Arch/aarch64/Utils.h>
namespace Prekernel {
// "12.1 System Timer Registers" / "10.2 System Timer Registers"
struct TimerRegisters {
u32 control_and_status;
u32 counter_low;
u32 counter_high;
u32 compare[4];
};
// Bits of the `control_and_status` register.
// See "CS register" in Broadcom doc for details.
enum FlagBits {
SystemTimerMatch0 = 1 << 0,
SystemTimerMatch1 = 1 << 1,
SystemTimerMatch2 = 1 << 2,
SystemTimerMatch3 = 1 << 3,
};
Timer::Timer()
: m_registers(MMIO::the().peripheral<TimerRegisters>(0x3000))
{
}
Timer& Timer::the()
{
static Timer instance;
return instance;
}
u64 Timer::microseconds_since_boot()
{
u32 high = m_registers->counter_high;
u32 low = m_registers->counter_low;
if (high != m_registers->counter_high) {
high = m_registers->counter_high;
low = m_registers->counter_low;
}
return (static_cast<u64>(high) << 32) | low;
}
class SetClockRateMboxMessage : Prekernel::Mailbox::Message {
public:
u32 clock_id;
u32 rate_hz;
u32 skip_setting_turbo;
SetClockRateMboxMessage()
: Prekernel::Mailbox::Message(0x0003'8002, 12)
{
clock_id = 0;
rate_hz = 0;
skip_setting_turbo = 0;
}
};
u32 Timer::set_clock_rate(ClockID clock_id, u32 rate_hz, bool skip_setting_turbo)
{
struct __attribute__((aligned(16))) {
Prekernel::Mailbox::MessageHeader header;
SetClockRateMboxMessage set_clock_rate;
Prekernel::Mailbox::MessageTail tail;
} message_queue;
message_queue.set_clock_rate.clock_id = static_cast<u32>(clock_id);
message_queue.set_clock_rate.rate_hz = rate_hz;
message_queue.set_clock_rate.skip_setting_turbo = skip_setting_turbo ? 1 : 0;
if (!Prekernel::Mailbox::the().send_queue(&message_queue, sizeof(message_queue))) {
warnln("Timer::set_clock_rate() failed!");
return 0;
}
return message_queue.set_clock_rate.rate_hz;
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Prekernel {
struct TimerRegisters;
class Timer {
public:
static Timer& the();
u64 microseconds_since_boot();
enum class ClockID {
Reserved = 0,
EMMC = 1,
UART = 2,
ARM = 3,
CORE = 4,
V3D = 5,
H264 = 6,
ISP = 7,
SDRAM = 8,
PIXEL = 9,
PWM = 10,
HEVC = 11,
EMMC2 = 12,
M2MC = 13,
PIXEL_BVB = 14,
};
u32 set_clock_rate(ClockID, u32 rate_hz, bool skip_setting_turbo = true);
private:
Timer();
TimerRegisters volatile* m_registers;
};
}

View file

@ -0,0 +1,162 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/aarch64/GPIO.h>
#include <Kernel/Arch/aarch64/MMIO.h>
#include <Kernel/Arch/aarch64/Timer.h>
#include <Kernel/Arch/aarch64/UART.h>
namespace Prekernel {
// "13.4 Register View" / "11.5 Register View"
struct UARTRegisters {
u32 data;
u32 receive_status_or_error_clear;
u32 unused[4];
u32 flag;
u32 unused2;
u32 unused_ilpr;
u32 integer_baud_rate_divisor; // Only the lowest 16 bits are used.
u32 fractional_baud_rate_divisor; // Only the lowest 6 bits are used.
u32 line_control;
u32 control;
u32 interrupt_fifo_level_select;
u32 interrupt_mask_set_clear;
u32 raw_interrupt_status;
u32 masked_interrupt_status;
u32 interrupt_clear;
u32 dma_control;
u32 test_control;
};
// Bits of the `flag` register.
// See "FR register" in Broadcom doc for details.
enum FlagBits {
ClearToSend = 1 << 0,
UnsupportedDSR = 1 << 1,
UnsupportedDCD = 1 << 2,
UARTBusy = 1 << 3,
ReceiveFifoEmpty = 1 << 4,
TransmitFifoFull = 1 << 5,
ReceiveFifoFull = 1 << 6,
TransmitFifoEmpty = 1 << 7,
};
// Bits for the `line_control` register.
// See "LCRH register" in Broadcom doc for details.
enum LineControlBits {
SendBreak = 1 << 0,
EnableParityCheckingAndGeneration = 1 << 1,
EvenParity = 1 << 2,
TransmitTwoStopBits = 1 << 3,
EnableFIFOs = 1 << 4,
WordLength5Bits = 0b00 << 5,
WordLength6Bits = 0b01 << 5,
WordLength7Bits = 0b10 << 5,
WordLength8Bits = 0b11 << 5,
StickParity = 1 << 7,
};
// Bits for the `control` register.
// See "CR register" in Broadcom doc for details. From there:
// NOTE: Program the control registers as follows:
// 1. Disable the UART.
// 2. Wait for the end of transmission or reception of the current character.
// 3. Flush the transmit FIFO by setting the FEN bit to 0 in the Line Control Register, UART_LCRH.
// 4. Reprogram the Control Register, UART_CR.
// 5. Enable the UART
enum ControlBits {
UARTEnable = 1 << 0,
UnsupportedSIREN = 1 << 1,
UnsupportedSIRLP = 1 << 2,
// Bits 3-6 are reserved.
LoopbackEnable = 1 << 7,
TransmitEnable = 1 << 8,
ReceiveEnable = 1 << 9,
UnsupportedDTR = 1 << 10,
RequestToSend = 1 << 11,
UnsupportedOut1 = 1 << 12,
UnsupportedOut2 = 1 << 13,
RTSHardwareFlowControlEnable = 1 << 14,
CTSHardwareFlowControlEnable = 1 << 15,
};
UART::UART()
: m_registers(MMIO::the().peripheral<UARTRegisters>(0x20'1000))
{
// Disable UART while changing configuration.
m_registers->control = 0;
// FIXME: Should wait for current transmission to end and should flush FIFO.
constexpr int baud_rate = 115'200;
// Set UART clock so that the baud rate divisor ends up as 1.0.
// FIXME: Not sure if this is a good UART clock rate.
u32 rate_in_hz = Timer::the().set_clock_rate(Timer::ClockID::UART, 16 * baud_rate);
// The BCM's PL011 UART is alternate function 0 on pins 14 and 15.
auto& gpio = Prekernel::GPIO::the();
gpio.set_pin_function(14, Prekernel::GPIO::PinFunction::Alternate0);
gpio.set_pin_function(15, Prekernel::GPIO::PinFunction::Alternate0);
gpio.set_pin_pull_up_down_state(Array { 14, 15 }, Prekernel::GPIO::PullUpDownState::Disable);
// Clock and pins are configured. Turn UART on.
set_baud_rate(baud_rate, rate_in_hz);
m_registers->line_control = EnableFIFOs | WordLength8Bits;
m_registers->control = UARTEnable | TransmitEnable | ReceiveEnable;
}
UART& UART::the()
{
static UART instance;
return instance;
}
void UART::send(u32 c)
{
wait_until_we_can_send();
m_registers->data = c;
}
u32 UART::receive()
{
wait_until_we_can_receive();
// Mask out error bits.
return m_registers->data & 0xFF;
}
void UART::set_baud_rate(int baud_rate, int uart_frequency_in_hz)
{
// Broadcom doc: """Baud rate divisor BAUDDIV = (FUARTCLK/(16 * Baud rate))""".
// BAUDDIV is stored as a 16.6 fixed point value, so do computation scaled by (1 << 6) == 64.
// 64*(FUARTCLK/(16 * Baud rate)) == 4*FUARTCLK/(Baud rate). For rounding, add 0.5 == (Baud rate/2)/(Baud rate).
u32 baud_rate_divisor_fixed_point = (4 * uart_frequency_in_hz + baud_rate / 2) / baud_rate;
m_registers->integer_baud_rate_divisor = baud_rate_divisor_fixed_point / 64;
m_registers->fractional_baud_rate_divisor = baud_rate_divisor_fixed_point % 64;
}
void UART::wait_until_we_can_send()
{
while (m_registers->flag & TransmitFifoFull)
;
}
void UART::wait_until_we_can_receive()
{
while (m_registers->flag & ReceiveFifoEmpty)
;
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2021, Nico Weber <thakis@chromium.org>
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Prekernel {
struct UARTRegisters;
// Abstracts the PL011 UART on a Raspberry Pi.
// (The BCM2711 on a Raspberry Pi 4 has five PL011 UARTs; this is always the first of those.)
class UART {
public:
static UART& the();
void send(u32 c);
u32 receive();
void print_str(char const* s)
{
while (*s)
send(*s++);
}
void print_num(u64 n)
{
char buf[21];
int i = 0;
do {
buf[i++] = (n % 10) + '0';
n /= 10;
} while (n);
for (i--; i >= 0; i--)
send(buf[i]);
}
void print_hex(u64 n)
{
char buf[17];
static char const* digits = "0123456789ABCDEF";
int i = 0;
do {
buf[i++] = digits[n % 16];
n /= 16;
} while (n);
send(static_cast<u32>('0'));
send(static_cast<u32>('x'));
buf[16] = '\0';
for (i--; i >= 0; i--) {
send(buf[i]);
}
}
private:
UART();
void set_baud_rate(int baud_rate, int uart_frequency_in_hz);
void wait_until_we_can_send();
void wait_until_we_can_receive();
UARTRegisters volatile* m_registers;
};
}