Compare commits

...

29 Commits

Author SHA1 Message Date
1d973664ed
kernel/ATA: Pass extra information to DeviceRegistry
All checks were successful
continuous-integration/drone/pr Build is passing
This is needed since merging e7d482e from main.
2023-05-20 21:50:45 +02:00
3423599ec7
kernel+init: Create a device node in /dev to access the CDROM from userspace!
Still using PIO, though.
2023-05-20 21:50:45 +02:00
444ac01d01
kernel/ATA: Read the CDROM's first sector using ATAPI PIO!!
Sadly, for some reason, DMA is not working right now.
This is a problem, as PIO is inconvenient. But hey, it works for now!
2023-05-20 21:50:45 +02:00
7a4b36d795
kernel/x86_64: Implement writing to PCI fields 2023-05-20 21:50:45 +02:00
913ce18121
kernel/PCI: Add bit enum for the Command field 2023-05-20 21:50:45 +02:00
6ba8af6a54
kernel: Actually register interrupt handlers properly 2023-05-20 21:50:44 +02:00
599193c1bf
kernel/ATA: Calculate block sizes for ATA devices as well 2023-05-20 21:50:44 +02:00
8449a7e545
kernel/ATA: Send a READ CAPACITY packet to an ATA drive on initialization 2023-05-20 21:50:44 +02:00
8fb9909de9
kernel/ATA: Read the PCI Busmaster registers and start preparing for DMA 2023-05-20 21:50:44 +02:00
2c4d20b30b
kernel/ATA: Read the Busmaster base port and verify it 2023-05-20 21:50:44 +02:00
61f1f16be7
kernel: Handle device BARs properly 2023-05-20 21:50:44 +02:00
d872f78b67
kernel/ATA: Read ATA strings properly instead of backwards
Now we can see the model string. What does it say...

"QEMU DVD-ROM". Let's go!
2023-05-20 21:50:43 +02:00
e8ceab1896
kernel/ATA: Implement enough to send an IDENTIFY command and read the model number :) 2023-05-20 21:50:43 +02:00
f7b7c5574e
kernel/ATA: Handle drive IRQs in compatibility mode 2023-05-20 21:50:43 +02:00
e57e79cb3e
kernel/CPU: Allow passing arbitrary data to interrupt handlers 2023-05-20 21:50:42 +02:00
8f90d5a8b7
kernel/ATA: Start reading/writing registers and detecting drives 2023-05-20 21:50:42 +02:00
2f828f83ee
kernel: Warn if no ATA controller is found 2023-05-20 21:50:42 +02:00
c4428f15bc
kernel: Add a KMutex class and use that for ATA::Controller locking 2023-05-20 21:50:42 +02:00
94569f2171
kernel/x86_64: Add basic ATA controller and channel identification 2023-05-20 21:50:41 +02:00
3586350754
kernel/PCI: Add more PCI field types 2023-05-20 21:50:41 +02:00
f4f0ecdb42
kernel/x86_64: Add a way to register IRQ handlers from other kernel subsystems 2023-05-20 21:50:41 +02:00
597aada09e
ls+stat: Handle symbolic links properly
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-20 21:49:25 +02:00
4ec1af5e51
apps: Add ln 2023-05-20 21:49:25 +02:00
7a8ef52408
libos: Support not following symlinks 2023-05-20 21:49:25 +02:00
cb205c851c
libc: Add symlink(), symlinkat(), and lstat() 2023-05-20 21:49:25 +02:00
67a9d130e2
kernel: Add initial support for symbolic links :D 2023-05-20 21:49:24 +02:00
b75bd4cd14
libluna: Add PathParser::has_next() 2023-05-20 16:39:18 +02:00
9bb5371e8c
libluna+libc: Add strtok_r() 2023-05-20 16:37:07 +02:00
47d505dcbb
libc: Add getline() and getdelim()
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-20 15:36:30 +02:00
48 changed files with 1847 additions and 67 deletions

View File

@ -28,3 +28,4 @@ luna_app(mount.cpp mount)
luna_app(umount.cpp umount)
luna_app(ps.cpp ps)
luna_app(time.cpp time)
luna_app(ln.cpp ln)

24
apps/ln.cpp Normal file
View File

@ -0,0 +1,24 @@
#include <os/ArgumentParser.h>
#include <stdio.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
{
StringView target;
StringView link;
os::ArgumentParser parser;
parser.add_description("Create symbolic links.");
parser.add_system_program_info("ln"_sv);
parser.add_positional_argument(target, "target", true);
parser.add_positional_argument(link, "linkpath", true);
parser.parse(argc, argv);
if (symlink(target.chars(), link.chars()) < 0)
{
perror("ln");
return 1;
}
return 0;
}

View File

@ -64,6 +64,7 @@ Result<int> luna_main(int argc, char** argv)
bool long_list { false };
bool human_readable { false };
bool si { false };
bool follow_symlink_args { false };
os::ArgumentParser parser;
parser.add_description("List files contained in a directory (defaults to '.', the current directory)"_sv);
@ -75,13 +76,15 @@ Result<int> luna_main(int argc, char** argv)
parser.add_switch_argument(human_readable, 'h', "human-readable"_sv,
"with -l, show human-readable sizes e.g. 2KiB, 6GiB"_sv);
parser.add_switch_argument(si, ' ', "si"_sv, "same as -h, but show sizes in powers of 10"_sv);
parser.add_switch_argument(follow_symlink_args, 'H', "dereference-args"_sv,
"follow symbolic links listed as arguments"_sv);
parser.parse(argc, argv);
Vector<String> files;
int dirfd = AT_FDCWD;
SharedPtr<os::Directory> dir;
if (os::FileSystem::is_directory(pathname))
if (os::FileSystem::is_directory(pathname, follow_symlink_args))
{
dir = TRY(os::Directory::open(pathname));
dirfd = dir->fd();
@ -93,7 +96,7 @@ Result<int> luna_main(int argc, char** argv)
files = TRY(dir->list(filter));
}
else if (os::FileSystem::exists(pathname))
else if (os::FileSystem::exists(pathname, follow_symlink_args))
{
auto str = TRY(String::from_string_view(pathname));
TRY(files.try_append(move(str)));
@ -111,7 +114,7 @@ Result<int> luna_main(int argc, char** argv)
for (const auto& file : files)
{
struct stat st;
TRY(os::FileSystem::stat({ dirfd, file.view() }, st));
TRY(os::FileSystem::stat({ dirfd, file.view() }, st, false));
StringView owner;
StringView group;

View File

@ -10,6 +10,7 @@ static const char* file_type(mode_t mode)
case S_IFREG: return "regular file";
case S_IFDIR: return "directory";
case S_IFCHR: return "character special device";
case S_IFLNK: return "symbolic link";
default: return "unknown file type";
}
}
@ -17,15 +18,17 @@ static const char* file_type(mode_t mode)
Result<int> luna_main(int argc, char** argv)
{
StringView path;
bool follow_symlinks { false };
os::ArgumentParser parser;
parser.add_description("Display file status.");
parser.add_system_program_info("stat"_sv);
parser.add_positional_argument(path, "path", true);
parser.add_switch_argument(follow_symlinks, 'L', "dereference"_sv, "follow symlinks");
parser.parse(argc, argv);
struct stat st;
TRY(os::FileSystem::stat(path, st));
TRY(os::FileSystem::stat(path, st, follow_symlinks));
printf(" File: %s\n", path.chars());
printf(" Size: %zu (%s)\n", st.st_size, file_type(st.st_mode));

View File

@ -33,7 +33,7 @@ char* getpass()
return nullptr;
}
static char buf[1024];
static char buf[BUFSIZ];
char* rc = fgets(buf, sizeof(buf), stdin);
restore_terminal();

View File

@ -64,6 +64,7 @@ if("${LUNA_ARCH}" MATCHES "x86_64")
src/arch/x86_64/Thread.cpp
src/arch/x86_64/PCI.cpp
src/arch/x86_64/Keyboard.cpp
src/arch/x86_64/disk/ATA.cpp
src/arch/x86_64/init/GDT.cpp
src/arch/x86_64/init/IDT.cpp
src/arch/x86_64/init/PIC.cpp

View File

@ -22,6 +22,9 @@ namespace CPU
void disable_interrupts();
void wait_for_interrupt();
bool save_interrupts();
void restore_interrupts(bool saved);
void get_stack_trace(void (*callback)(u64, void*), void* arg);
void print_stack_trace();
void get_stack_trace_at(Registers* regs, void (*callback)(u64, void*), void* arg);
@ -29,5 +32,8 @@ namespace CPU
u16 get_processor_id();
bool register_interrupt(u8 interrupt, void (*handler)(Registers*, void*), void* context);
void sync_interrupts();
void pause();
}

View File

@ -9,6 +9,10 @@ struct ScanInfo
namespace PCI
{
BAR::BAR(u32 raw) : m_raw(raw)
{
}
Device::ID read_id(const Device::Address& address)
{
const u16 vendor = read16(address, Field::VendorID);
@ -108,4 +112,13 @@ namespace PCI
}
}
}
BAR Device::getBAR(u8 index) const
{
check(index < 6);
u32 raw = read32(address, 0x10 + (index * 4));
return { raw };
}
}

View File

@ -17,7 +17,67 @@ namespace PCI
HeaderType = 0x0e,
SecondaryBus = 0x19
BAR0 = 0x10,
BAR1 = 0x14,
BAR2 = 0x18,
BAR3 = 0x1c,
SecondaryBus = 0x19,
InterruptLine = 0x3c,
};
enum CommandField : u16
{
CMD_IO_SPACE = 1 << 0,
CMD_MEMORY_SPACE = 1 << 1,
CMD_BUS_MASTER = 1 << 2,
CMD_SPECIAL_CYCLES = 1 << 3,
CMD_MEMORY_WRITE_AND_INVALIDATE = 1 << 4,
CMD_VGA_PALETTE_SNOOP = 1 << 5,
CMD_PARITY_ERROR_RESPONSE = 1 << 6,
CMD_SERR = 1 << 8,
CMD_FAST_BACK_TO_BACK = 1 << 9,
CMD_INTERRUPT_DISABLE = 1 << 10,
};
struct BAR
{
public:
BAR(u32 raw);
bool is_iospace()
{
return m_raw & 0x01;
}
bool is_memory_space()
{
return !is_iospace();
}
u16 port()
{
return (u16)(m_raw & 0xfffffffc);
}
u8 type()
{
return (m_raw >> 1) & 0x03;
}
bool is_prefetchable()
{
return m_raw & (1 << 3);
}
u32 address_32bit()
{
return m_raw & 0xFFFFFFF0;
}
private:
u32 m_raw;
};
struct Device
@ -42,6 +102,8 @@ namespace PCI
u32 function;
};
BAR getBAR(u8 index) const;
ID id;
Type type;
Address address;

View File

@ -188,4 +188,18 @@ ISR_ERROR 21 ; control-protection exception (#CP)
; ISR 22-31 reserved
IRQ 32, 0 ; timer interrupt
IRQ 33, 1 ; keyboard interrupt
IRQ 34, 2
IRQ 35, 3
IRQ 36, 4
IRQ 37, 5
IRQ 38, 6
IRQ 39, 7
IRQ 40, 8
IRQ 41, 9
IRQ 42, 10
IRQ 43, 11
IRQ 44, 12
IRQ 45, 13
IRQ 46, 14
IRQ 47, 15
ISR 66 ; system call

View File

@ -22,12 +22,23 @@ extern "C" void enable_nx();
extern void setup_gdt();
extern void remap_pic();
extern void change_pic_masks(u8 pic1_mask, u8 pic2_mask);
extern void pic_eoi(unsigned char irq);
extern void pic_eoi(Registers* regs);
extern void setup_idt();
static Thread* g_io_thread;
typedef void (*interrupt_handler_t)(Registers*, void*);
struct InterruptHandler
{
interrupt_handler_t function;
void* context;
};
static InterruptHandler irq_handlers[16];
void FPData::save()
{
asm volatile("fxsave (%0)" : : "r"(m_data));
@ -135,21 +146,34 @@ void io_thread()
}
}
// Called from _asm_interrupt_entry
extern "C" void arch_interrupt_entry(Registers* regs)
{
if (regs->isr < 32) handle_x86_exception(regs);
else if (regs->isr == 32) // Timer interrupt
static void timer_interrupt(Registers* regs, void*)
{
Timer::tick();
if (should_invoke_scheduler()) Scheduler::invoke(regs);
pic_eoi(regs);
}
else if (regs->isr == 33) // Keyboard interrupt
static void keyboard_interrupt(Registers*, void*)
{
u8 scancode = IO::inb(0x60);
scancode_queue.try_push(scancode);
g_io_thread->wake_up();
}
// Called from _asm_interrupt_entry
extern "C" void arch_interrupt_entry(Registers* regs)
{
if (regs->isr < 32) handle_x86_exception(regs);
else if (regs->isr >= 32 && regs->isr < 48) // IRQ from the PIC
{
u64 irq = regs->error;
auto handler = irq_handlers[irq];
if (!handler.function)
{
kwarnln("Unhandled IRQ catched! Halting.");
CPU::efficient_halt();
}
handler.function(regs, handler.context);
pic_eoi(regs);
}
else if (regs->isr == 66) // System call
@ -159,7 +183,7 @@ extern "C" void arch_interrupt_entry(Registers* regs)
}
else
{
kwarnln("IRQ catched! Halting.");
kwarnln("Unhandled interrupt catched! Halting.");
CPU::efficient_halt();
}
}
@ -218,6 +242,10 @@ namespace CPU
kwarnln("not setting the NX bit as it is unsupported");
setup_gdt();
setup_idt();
memset(irq_handlers, 0, sizeof(irq_handlers));
register_interrupt(0, timer_interrupt, nullptr);
register_interrupt(1, keyboard_interrupt, nullptr);
}
void platform_finish_init()
@ -226,6 +254,8 @@ namespace CPU
.expect_value("Could not create the IO background thread!");
remap_pic();
sync_interrupts();
}
void enable_interrupts()
@ -238,6 +268,20 @@ namespace CPU
asm volatile("cli");
}
bool save_interrupts()
{
u64 flags;
asm volatile("pushfq; pop %0" : "=r"(flags));
return flags & 0x200;
}
void restore_interrupts(bool saved)
{
if (saved) enable_interrupts();
else
disable_interrupts();
}
void wait_for_interrupt()
{
asm volatile("hlt");
@ -338,6 +382,35 @@ namespace CPU
__get_cpuid(1, &unused, &ebx, &unused, &unused);
return (u16)(ebx >> 24);
}
bool register_interrupt(u8 interrupt, interrupt_handler_t handler, void* context)
{
if (irq_handlers[interrupt].function) return false;
irq_handlers[interrupt] = { handler, context };
sync_interrupts();
return true;
}
void sync_interrupts()
{
u8 pic1_mask, pic2_mask;
pic1_mask = pic2_mask = 0b11111111;
for (int i = 0; i < 8; i++)
{
if (irq_handlers[i].function) pic1_mask &= (u8)(~(1 << i));
if (irq_handlers[i + 8].function) pic2_mask &= (u8)(~(1 << i));
}
if (pic2_mask != 0b11111111) pic1_mask &= 0b11111011;
auto val = CPU::save_interrupts();
CPU::disable_interrupts();
change_pic_masks(pic1_mask, pic2_mask);
CPU::restore_interrupts(val);
}
}
// called by kernel_yield

View File

@ -33,19 +33,32 @@ namespace PCI
void write8(const Device::Address& address, u32 field, u8 value)
{
ignore(address, field, value);
todo();
u8 offset = (u8)(field & ~0x3);
union {
u8 split[4];
u32 full;
};
full = read32(address, offset);
split[(field & 0x3)] = value;
write32(address, offset, full);
}
void write16(const Device::Address& address, u32 field, u8 value)
void write16(const Device::Address& address, u32 field, u16 value)
{
ignore(address, field, value);
todo();
u8 offset = (u8)(field & ~0x3);
union {
u8 split[4];
u32 full;
};
full = read32(address, offset);
split[(field & 0x3)] = (u8)(value >> 8);
split[(field & 0x3) + 1] = (u8)(value & 0xff);
write32(address, offset, full);
}
void write32(const Device::Address& address, u32 field, u8 value)
void write32(const Device::Address& address, u32 field, u32 value)
{
ignore(address, field, value);
todo();
IO::outl(PCI_ADDRESS_PORT, make_pci_address(address, field));
IO::outl(PCI_VALUE_PORT, value);
}
}

View File

@ -0,0 +1,780 @@
#include "arch/x86_64/disk/ATA.h"
#include "Log.h"
#include "arch/Serial.h"
#include "arch/Timer.h"
#include "arch/x86_64/IO.h"
#include "memory/MemoryManager.h"
#include <luna/Alignment.h>
#include <luna/CType.h>
#include <luna/SafeArithmetic.h>
#include <luna/Vector.h>
SharedPtr<ATA::Controller> g_controller;
static void irq_handler(Registers* regs, void* ctx)
{
((ATA::Channel*)ctx)->irq_handler(regs);
}
static usize copy_ata_string(char* out, u16* in, usize size)
{
for (usize i = 0; i < size; i += 2)
{
u16 val = in[i / 2];
out[i] = (u8)(val >> 8);
out[i + 1] = (u8)(val & 0xff);
}
out[size + 1] = '\0';
return size;
}
namespace ATA
{
Result<void> Controller::scan()
{
// FIXME: Propagate errors.
PCI::scan(
[](const PCI::Device& device) {
if (!g_controller)
{
auto controller = adopt_shared_if_nonnull(new (std::nothrow) Controller(device)).release_value();
kinfoln("ata: Found ATA controller on PCI bus (%x:%x:%x)", device.address.bus,
device.address.function, device.address.slot);
if (controller->initialize()) g_controller = controller;
}
},
{ .klass = 1, .subclass = 1 });
if (!g_controller) kwarnln("ata: No ATA controller found.");
return {};
}
bool Controller::initialize()
{
u16 command_old = PCI::read16(m_device.address, PCI::Command);
u16 command_new = command_old;
command_new &= ~PCI::CMD_INTERRUPT_DISABLE;
command_new |= PCI::CMD_IO_SPACE;
command_new |= PCI::CMD_BUS_MASTER;
if (command_new != command_old) PCI::write16(m_device.address, PCI::Command, command_new);
if (!m_primary_channel.initialize()) return false;
return m_secondary_channel.initialize();
}
Controller::Controller(const PCI::Device& device)
: m_device(device), m_primary_channel(this, 0, {}), m_secondary_channel(this, 1, {})
{
}
Channel::Channel(Controller* controller, u8 channel_index, Badge<Controller>)
: m_controller(controller), m_channel_index(channel_index)
{
}
u8 Channel::read_register(Register reg)
{
return IO::inb(m_io_base + (u16)reg);
}
u16 Channel::read_data()
{
return IO::inw(m_io_base + (u16)Register::Data);
}
void Channel::write_data(u16 value)
{
IO::outw(m_io_base + (u16)Register::Data, value);
}
void Channel::write_register(Register reg, u8 value)
{
IO::outb(m_io_base + (u16)reg, value);
}
u8 Channel::read_control(ControlRegister reg)
{
return IO::inb(m_control_base + (u16)reg);
}
void Channel::write_control(ControlRegister reg, u8 value)
{
IO::outb(m_control_base + (u16)reg, value);
}
u8 Channel::read_bm(BusmasterRegister reg)
{
return IO::inb(m_busmaster_base + (u16)reg);
}
void Channel::write_bm(BusmasterRegister reg, u8 value)
{
IO::outb(m_busmaster_base + (u16)reg, value);
}
u32 Channel::read_prdt_address()
{
return IO::inl(m_busmaster_base + (u16)BusmasterRegister::PRDTAddress);
}
void Channel::write_prdt_address(u32 value)
{
IO::outl(m_busmaster_base + (u16)BusmasterRegister::PRDTAddress, value);
}
void Channel::delay_400ns()
{
// FIXME: We should use kernel_sleep(), but it doesn't support nanosecond granularity.
for (int i = 0; i < 14; i++) { [[maybe_unused]] volatile u8 val = read_control(ControlRegister::AltStatus); }
}
void Channel::select(u8 drive)
{
if (drive == m_current_drive) return;
u8 value = (drive << 4) | 0xa0;
write_register(Register::DriveSelect, value);
delay_400ns();
m_current_drive = drive;
}
void Channel::irq_handler(Registers*)
{
// FIXME: Read the Busmaster register to make sure the IRQ is for this channel.
if (m_current_drive < 2 && m_drives[m_current_drive]) m_drives[m_current_drive]->irq_handler();
m_irq_called = true;
if (m_thread) m_thread->wake_up();
}
void Channel::prepare_for_irq()
{
m_thread = Scheduler::current();
m_irq_called = false;
}
void Channel::wait_for_irq()
{
if (!m_irq_called) kernel_wait_for_event();
m_irq_called = false;
}
bool Channel::wait_for_irq_or_timeout(u64 timeout)
{
if (!m_irq_called)
{
kernel_sleep(timeout);
m_irq_called = false;
return m_thread->sleep_ticks_left;
}
m_irq_called = false;
return true;
}
bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout)
{
u64 begin = Timer::ticks_ms();
while (true)
{
u8 reg_value = reg == Register::Status ? read_control(ControlRegister::AltStatus) : read_register(reg);
if (reg_value & value) return true;
if ((Timer::ticks_ms() - begin) >= timeout) return false;
kernel_sleep(1);
}
}
bool Channel::wait_for_reg_clear(Register reg, u8 value, u64 timeout)
{
u64 begin = Timer::ticks_ms();
while (true)
{
u8 reg_value = reg == Register::Status ? read_control(ControlRegister::AltStatus) : read_register(reg);
if ((reg_value & value) == 0) return true;
if ((Timer::ticks_ms() - begin) >= timeout) return false;
kernel_sleep(1);
}
}
Result<void> Channel::wait_until_ready()
{
if (!wait_for_reg_clear(Register::Status, SR_Busy, 1000))
{
kwarnln("ata: Drive %d:%d timed out (BSY)", m_channel_index, m_current_drive);
return err(EIO);
}
if (!wait_for_reg_set(Register::Status, SR_DataRequestReady | SR_Error, 1000))
{
kwarnln("ata: Drive %d:%d timed out (DRQ)", m_channel_index, m_current_drive);
return err(EIO);
}
u8 status = read_control(ControlRegister::AltStatus);
if (status & SR_Error)
{
kwarnln("ata: An error occurred in drive %d:%d while waiting for data to become available", m_channel_index,
m_current_drive);
return err(EIO);
}
return {};
}
bool Channel::initialize()
{
int offset = m_channel_index ? 2 : 0;
m_is_pci_native_mode = m_controller->device().type.prog_if & (1 << offset);
u16 control_port_base_address;
u16 io_base_address;
if (m_is_pci_native_mode)
{
auto io_base = m_controller->device().getBAR(m_channel_index ? 2 : 0);
if (!io_base.is_iospace())
{
kwarnln("ata: Channel %d's IO base BAR is not in IO space", m_channel_index);
return false;
}
io_base_address = io_base.port();
auto io_control = m_controller->device().getBAR(m_channel_index ? 3 : 1);
if (!io_control.is_iospace())
{
kwarnln("ata: Channel %d's control base BAR is not in IO space", m_channel_index);
return false;
}
control_port_base_address = io_control.port() + 2;
}
else
{
io_base_address = m_channel_index ? 0x170 : 0x1f0;
control_port_base_address = m_channel_index ? 0x376 : 0x3f6;
}
m_io_base = io_base_address;
m_control_base = control_port_base_address;
auto io_busmaster = m_controller->device().getBAR(4);
if (!io_busmaster.is_iospace())
{
kwarnln("ata: Channel %d's busmaster base BAR is not in IO space", m_channel_index);
return false;
}
m_busmaster_base = io_busmaster.port() + (u16)(m_channel_index * 8u);
if (m_is_pci_native_mode) m_interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine);
else
m_interrupt_line = m_channel_index ? 15 : 14;
write_control(ControlRegister::DeviceControl, 0);
for (u8 drive = 0; drive < 2; drive++)
{
ScopedKMutexLock<100> lock(m_lock);
select(drive);
if (read_register(Register::Status) == 0)
{
// No drive on this slot.
continue;
}
kinfoln("ata: Channel %d has a drive on slot %d!", m_channel_index, drive);
auto rc = adopt_shared_if_nonnull(new (std::nothrow) Drive(this, drive, {}));
if (rc.has_error())
{
kinfoln("ata: Failed to create drive object: %s", rc.error_string());
return false;
}
m_drives[drive] = rc.release_value();
if (!m_drives[drive]->initialize())
{
m_drives[drive] = {};
return false;
}
}
bool ok = CPU::register_interrupt(m_interrupt_line, ::irq_handler, this);
if (!ok)
{
kerrorln("ata: Failed to register IRQ handler for ATA channel %d (IRQ %d)", m_channel_index,
m_interrupt_line);
return false;
}
for (u8 drive = 0; drive < 2; drive++)
{
if (m_drives[drive])
{
if (!m_drives[drive]->post_initialize())
{
m_drives[drive] = {};
return false;
}
if (ATADevice::create(m_drives[drive]).has_error())
{
kwarnln("ata: Failed to register ATA drive %d:%d in DeviceRegistry", m_channel_index, drive);
continue;
}
}
}
return true;
}
Drive::Drive(Channel* channel, u8 drive_index, Badge<Channel>) : m_channel(channel), m_drive_index(drive_index)
{
}
bool Drive::identify_ata()
{
m_channel->write_register(Register::Command, m_is_atapi ? CMD_Identify_Packet : CMD_Identify);
m_channel->delay_400ns();
if (!m_channel->wait_for_reg_clear(Register::Status, SR_Busy, 1000))
{
kwarnln("ata: Drive %d timed out clearing SR_Busy (waited for 1000 ms)", m_drive_index);
return false;
}
if (m_channel->read_register(Register::Status) & SR_Error)
{
u8 lbam = m_channel->read_register(Register::LBAMiddle);
u8 lbah = m_channel->read_register(Register::LBAHigh);
if ((lbam == 0x14 && lbah == 0xeb) || (lbam == 0x69 && lbah == 0x96))
{
if (!m_is_atapi)
{
kinfoln("ata: Drive %d is ATAPI, sending IDENTIFY_PACKET command", m_drive_index);
m_is_atapi = true;
return identify_ata();
}
}
kwarnln("ata: IDENTIFY command for drive %d returned error", m_drive_index);
return false;
}
if (!m_channel->wait_for_reg_set(Register::Status, SR_DataRequestReady | SR_Error, 1000))
{
kwarnln("ata: Drive %d timed out setting SR_DataRequestReady (waited for 1000 ms)", m_drive_index);
return false;
}
u8 status = m_channel->read_register(Register::Status);
if (status & SR_Error)
{
kwarnln("ata: IDENTIFY command for drive %d returned error", m_drive_index);
return false;
}
for (usize i = 0; i < 256; i++)
{
u16 data = m_channel->read_data();
m_identify_words[i] = data;
}
return true;
}
bool Drive::initialize()
{
m_channel->select(m_drive_index);
m_channel->write_register(Register::SectorCount, 0);
m_channel->write_register(Register::LBALow, 0);
m_channel->write_register(Register::LBAMiddle, 0);
m_channel->write_register(Register::LBAHigh, 0);
if (!identify_ata()) return false;
m_serial.set_length(copy_ata_string(m_serial.data(), &m_identify_words[10], SERIAL_LEN));
m_revision.set_length(copy_ata_string(m_revision.data(), &m_identify_words[23], REVISION_LEN));
m_model.set_length(copy_ata_string(m_model.data(), &m_identify_words[27], MODEL_LEN));
m_serial.trim(" ");
m_revision.trim(" ");
m_model.trim(" ");
kinfoln("ata: Drive IDENTIFY returned serial='%s', revision='%s' and model='%s'", m_serial.chars(),
m_revision.chars(), m_model.chars());
auto status = m_channel->read_bm(BusmasterRegister::Status);
if (status & BMS_SimplexOnly)
{
kwarnln("ata: Drive %d will not use DMA because of simplex shenanigans", m_drive_index);
m_uses_dma = false;
}
auto frame = MemoryManager::alloc_frame();
if (frame.has_error() || frame.value() > 0xffffffff)
{
kwarnln("ata: Failed to allocate memory below the 32-bit limit for the PRDT");
return false;
}
m_dma_prdt_phys = frame.release_value();
m_dma_prdt = (prdt_entry*)MMU::translate_physical_address(m_dma_prdt_phys);
memset(m_dma_prdt, 0, ARCH_PAGE_SIZE);
frame = MemoryManager::alloc_frame();
if (frame.has_error() || frame.value() > 0xffffffff)
{
kwarnln("ata: Failed to allocate memory below the 32-bit limit for DMA memory");
return false;
}
m_dma_mem_phys = frame.release_value();
m_dma_mem = (void*)MMU::translate_physical_address(m_dma_mem_phys);
memset(const_cast<void*>(m_dma_mem), 0, ARCH_PAGE_SIZE);
if (m_uses_dma)
{
auto cmd = m_channel->read_bm(BusmasterRegister::Command);
cmd &= ~BMC_StartStop;
m_channel->write_bm(BusmasterRegister::Command, cmd);
}
return true;
}
bool Drive::post_initialize()
{
if (m_is_atapi)
{
atapi_packet packet;
memset(&packet, 0, sizeof(packet));
packet.command_bytes[0] = ATAPI_ReadCapacity;
atapi_read_capacity_reply reply;
if (send_packet_atapi_pio(&packet, &reply, sizeof(reply)).has_error())
{
kwarnln("ata: Failed to send Read Capacity command to ATAPI drive");
return false;
}
m_is_lba48 = true;
// FIXME: This assumes the host machine is little-endian.
u32 last_lba = __builtin_bswap32(reply.last_lba);
u32 sector_size = __builtin_bswap32(reply.sector_size);
m_block_count = last_lba + 1;
m_block_size = sector_size;
}
else
{
if (m_identify_data.big_lba) m_is_lba48 = true;
if (m_is_lba48) m_block_count = m_identify_data.sectors_48;
else
m_block_count = m_identify_data.sectors_28;
// FIXME: Should we check for CHS?
// FIXME: Maybe a different block size is in use? Detect that.
m_block_size = 512;
}
u64 total_capacity;
if (!safe_mul(m_block_count, m_block_size).try_set_value(total_capacity))
{
kwarnln("ata: Drive %d's total capacity is too large", m_drive_index);
return false;
}
kinfoln("ata: Drive %d capacity information: Block Count=%lu, Block Size=%lu, Total Capacity=%lu",
m_drive_index, m_block_count, m_block_size, total_capacity);
char buf[2048];
if (atapi_read_pio(0, buf, sizeof(buf)).has_error()) return false;
char readable[2049];
for (int i = 0; i < 2048; i++) { readable[i] = _iscntrl(buf[i]) ? '.' : buf[i]; }
readable[2048] = 0;
kinfoln("ata: Read first sector from CD-ROM: %s", readable);
return true;
}
Result<void> Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size)
{
u8* ptr = (u8*)out;
m_channel->select(m_drive_index);
// We use PIO here.
m_channel->write_register(Register::Features, 0x00);
m_channel->write_register(Register::LBAMiddle, (u8)(response_size & 0xff));
m_channel->write_register(Register::LBAHigh, (u8)(response_size >> 8));
m_channel->write_register(Register::Command, CMD_Packet);
m_channel->delay_400ns();
usize i = 0;
TRY(m_channel->wait_until_ready());
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
while (i < response_size)
{
TRY(m_channel->wait_until_ready());
usize byte_count =
m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle);
usize word_count = byte_count / 2;
while (word_count--)
{
u16 value = m_channel->read_data();
ptr[0] = (u8)(value & 0xff);
ptr[1] = (u8)(value >> 8);
ptr += 2;
}
i += byte_count;
}
return {};
}
#if 0
Result<void> Drive::send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size)
{
check(m_uses_dma);
m_channel->select(m_drive_index);
kdbgln("have selected");
// We use DMA here.
m_channel->write_register(Register::Features, 0x01);
m_channel->write_register(Register::LBAMiddle, 0);
m_channel->write_register(Register::LBAHigh, 0);
kdbgln("will do_dma_command");
TRY(do_dma_command(CMD_Packet, response_size, false));
TRY(m_channel->wait_until_ready());
kdbgln("send atapi packet data");
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
kdbgln("do dma transfer");
TRY(do_dma_transfer());
memcpy(out, const_cast<void*>(m_dma_mem), response_size);
return {};
}
Result<void> Drive::do_dma_command(u8 command, u16 count, bool write)
{
m_dma_prdt->address = (u32)m_dma_mem_phys;
m_dma_prdt->count = count;
m_dma_prdt->flags = END_OF_PRDT;
kdbgln("ata: do_dma_command: phys=%x, command=%x, count=%u, write=%d", m_dma_prdt->address, command, count,
write);
m_channel->write_prdt_address((u32)m_dma_prdt_phys);
auto status = m_channel->read_bm(BusmasterRegister::Status);
status &= ~(BMS_DMAFailure | BMS_IRQPending);
m_channel->write_bm(BusmasterRegister::Status, status);
auto cmd = m_channel->read_bm(BusmasterRegister::Command);
if (!write) cmd |= BMC_ReadWrite;
else
cmd &= ~BMC_ReadWrite;
m_channel->write_bm(BusmasterRegister::Command, cmd);
m_channel->prepare_for_irq();
m_channel->write_register(Register::Command, command);
cmd = m_channel->read_bm(BusmasterRegister::Command);
cmd |= BMC_StartStop;
m_channel->write_bm(BusmasterRegister::Command, cmd);
m_channel->delay_400ns();
return {};
}
Result<void> Drive::do_dma_transfer()
{
if (!m_channel->wait_for_irq_or_timeout(2000))
{
kwarnln("ata: Drive %d timed out (DMA)", m_drive_index);
return err(EIO);
}
u8 status = m_channel->read_control(ControlRegister::AltStatus);
kdbgln("ata: status after irq: %#x", status);
m_channel->delay_400ns();
auto cmd = m_channel->read_bm(BusmasterRegister::Command);
cmd &= ~BMC_StartStop;
m_channel->write_bm(BusmasterRegister::Command, cmd);
status = m_channel->read_bm(BusmasterRegister::Status);
m_channel->write_bm(BusmasterRegister::Status, status & ~(BMS_DMAFailure | BMS_IRQPending));
if (status & BMS_DMAFailure)
{
kwarnln("ata: DMA failure while trying to read drive %d", m_drive_index);
return err(EIO);
}
return {};
}
#endif
Result<void> Drive::atapi_read_pio(u64 lba, void* out, usize size)
{
check(lba < m_block_count);
check(size <= ARCH_PAGE_SIZE);
atapi_packet read_packet;
memset(&read_packet, 0, sizeof(read_packet));
read_packet.command_bytes[0] = ATAPI_Read;
read_packet.command_bytes[2] = (lba >> 0x18) & 0xff;
read_packet.command_bytes[3] = (lba >> 0x10) & 0xff;
read_packet.command_bytes[4] = (lba >> 0x08) & 0xff;
read_packet.command_bytes[5] = (lba >> 0x00) & 0xff;
read_packet.command_bytes[9] = (u8)(size / m_block_size);
return send_packet_atapi_pio(&read_packet, out, (u16)size);
}
Result<void> Drive::read_lba(u64 lba, void* out, usize nblocks)
{
const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size;
if (m_is_atapi)
{
kdbgln("ata: Reading %zu blocks from ATAPI drive using PIO, at LBA %ld", nblocks, lba);
while (nblocks > blocks_per_page)
{
TRY(atapi_read_pio(lba, out, ARCH_PAGE_SIZE));
lba += blocks_per_page;
nblocks -= blocks_per_page;
out = offset_ptr(out, ARCH_PAGE_SIZE);
}
return atapi_read_pio(lba, out, nblocks * m_block_size);
}
else
todo();
}
void Drive::irq_handler()
{
// Clear the IRQ flag.
u8 status = m_channel->read_register(Register::Status);
if (status & SR_Error)
{
u8 error = m_channel->read_register(Register::Error);
(void)error;
}
if (m_uses_dma)
{
status = m_channel->read_bm(BusmasterRegister::Status);
if (status & BMS_DMAFailure) { kwarnln("ata: DMA failure in irq"); }
m_channel->write_bm(BusmasterRegister::Status, 4);
}
}
}
static u32 next_minor = 0;
Result<void> ATADevice::create(SharedPtr<ATA::Drive> drive)
{
auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) ATADevice()));
device->m_drive = drive;
return DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, "cdrom", 0400);
}
Result<u64> ATADevice::read(u8* buf, usize offset, usize size) const
{
if (size == 0) return 0;
if (offset > m_drive->capacity()) return 0;
if (offset + size > m_drive->capacity()) size = m_drive->capacity() - offset;
usize length = size;
auto block_size = m_drive->block_size();
auto* temp = TRY(make_array<u8>(block_size));
auto guard = make_scope_guard([temp] { delete[] temp; });
if (offset % block_size)
{
usize extra_size = block_size - (offset % block_size);
TRY(m_drive->read_lba(offset / block_size, temp, 1));
memcpy(buf, temp + (offset % block_size), extra_size);
offset += extra_size;
size -= extra_size;
buf += extra_size;
}
while (size >= ARCH_PAGE_SIZE)
{
TRY(m_drive->read_lba(offset / block_size, buf, ARCH_PAGE_SIZE / block_size));
offset += ARCH_PAGE_SIZE;
size -= ARCH_PAGE_SIZE;
buf += ARCH_PAGE_SIZE;
}
while (size >= block_size)
{
TRY(m_drive->read_lba(offset / block_size, buf, 1));
offset += block_size;
size -= block_size;
buf += block_size;
}
if (size)
{
TRY(m_drive->read_lba(offset / block_size, temp, 1));
memcpy(buf, temp, size);
}
return length;
}

View File

@ -0,0 +1,309 @@
#pragma once
#include "arch/PCI.h"
#include "fs/devices/DeviceRegistry.h"
#include "lib/KMutex.h"
#include <luna/Atomic.h>
#include <luna/SharedPtr.h>
#include <luna/StaticString.h>
namespace ATA
{
enum class Register : u16
{
Data = 0,
Error = 1,
Features = 1,
SectorCount = 2,
SectorNumber = 3,
LBALow = 3,
CylinderLow = 4,
LBAMiddle = 4,
CylinderHigh = 5,
LBAHigh = 5,
DriveSelect = 6,
Status = 7,
Command = 7,
};
enum class ControlRegister : u16
{
AltStatus = 0,
DeviceControl = 0,
DriveAddress = 1,
};
enum class BusmasterRegister : u16
{
Command = 0,
Status = 2,
PRDTAddress = 4,
};
enum StatusRegister : u8
{
SR_Busy = 0x80,
SR_DriveReady = 0x40,
SR_WriteFault = 0x20,
SR_SeekComplete = 0x10,
SR_DataRequestReady = 0x08,
SR_CorrectedData = 0x04,
SR_Index = 0x02,
SR_Error = 0x01
};
enum CommandRegister : u8
{
CMD_Identify = 0xec,
CMD_Packet = 0xa0,
CMD_Identify_Packet = 0xa1
};
enum BusMasterStatus : u8
{
BMS_SimplexOnly = 0x80,
BMS_SlaveInit = 0x40,
BMS_MasterInit = 0x20,
BMS_IRQPending = 0x4,
BMS_DMAFailure = 0x2,
BMS_DMAMode = 0x1
};
enum BusMasterCommand : u8
{
BMC_ReadWrite = 0x8,
BMC_StartStop = 0x1,
};
struct ATAIdentify
{
u16 flags;
u16 unused1[9];
char serial[20];
u16 unused2[3];
char firmware[8];
char model[40];
u16 sectors_per_int;
u16 unused3;
u16 capabilities[2];
u16 unused4[2];
u16 valid_ext_data;
u16 unused5[5];
u16 size_of_rw_mult;
u32 sectors_28;
u16 unused6[21];
u16 unused7 : 10;
u16 big_lba : 1;
u16 unused8 : 5;
u16 unused9[17];
u64 sectors_48;
u16 unused10[152];
};
enum ATAPICommand : u8
{
ATAPI_ReadCapacity = 0x25,
ATAPI_Read = 0xa8,
};
class Controller;
class Channel;
struct prdt_entry
{
u32 address;
u16 count;
u16 flags;
};
struct atapi_packet
{
union {
u16 command_words[6];
u8 command_bytes[12];
};
};
struct atapi_read_capacity_reply
{
u32 last_lba;
u32 sector_size;
};
static constexpr u16 END_OF_PRDT = (1 << 15);
class Drive
{
public:
Drive(Channel* channel, u8 drive_index, Badge<Channel>);
bool initialize();
bool post_initialize();
void irq_handler();
usize block_size() const
{
return m_block_size;
}
usize block_count() const
{
return m_block_count;
}
usize capacity() const
{
return m_block_count * m_block_size;
}
Result<void> read_lba(u64 lba, void* out, usize nblocks);
private:
bool identify_ata();
Result<void> send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size);
#if 0
Result<void> send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size);
Result<void> do_dma_command(u8 command, u16 count, bool write);
Result<void> do_dma_transfer();
#endif
Result<void> atapi_read_pio(u64 lba, void* out, usize size);
Channel* m_channel;
u8 m_drive_index;
union {
u16 m_identify_words[256];
ATAIdentify m_identify_data;
};
bool m_is_atapi { false };
bool m_uses_dma { true };
bool m_is_lba48;
u64 m_block_count;
u64 m_block_size;
prdt_entry* m_dma_prdt;
u64 m_dma_prdt_phys;
volatile void* m_dma_mem;
u64 m_dma_mem_phys;
constexpr static usize SERIAL_LEN = 20;
constexpr static usize REVISION_LEN = 8;
constexpr static usize MODEL_LEN = 40;
StaticString<SERIAL_LEN> m_serial;
StaticString<REVISION_LEN> m_revision;
StaticString<MODEL_LEN> m_model;
};
class Channel
{
public:
Channel(Controller* controller, u8 channel_index, Badge<Controller>);
u8 read_register(Register reg);
u16 read_data();
void write_data(u16 value);
void write_register(Register reg, u8 value);
u8 read_control(ControlRegister reg);
void write_control(ControlRegister reg, u8 value);
u8 read_bm(BusmasterRegister reg);
void write_bm(BusmasterRegister reg, u8 value);
u32 read_prdt_address();
void write_prdt_address(u32 value);
bool wait_for_reg_set(Register reg, u8 value, u64 timeout);
bool wait_for_reg_clear(Register reg, u8 value, u64 timeout);
Result<void> wait_until_ready();
void delay_400ns();
void prepare_for_irq();
void wait_for_irq();
bool wait_for_irq_or_timeout(u64 timeout);
void irq_handler(Registers*);
void select(u8 drive);
bool initialize();
private:
Controller* m_controller;
u8 m_channel_index;
bool m_is_pci_native_mode;
u8 m_interrupt_line;
KMutex<100> m_lock {};
Thread* m_thread { nullptr };
u16 m_io_base;
u16 m_control_base;
u16 m_busmaster_base;
bool m_irq_called { false };
u8 m_current_drive = (u8)-1;
SharedPtr<Drive> m_drives[2];
};
class Controller
{
public:
static Result<void> scan();
const PCI::Device& device() const
{
return m_device;
}
bool initialize();
private:
Controller(const PCI::Device& device);
PCI::Device m_device;
Channel m_primary_channel;
Channel m_secondary_channel;
};
}
class ATADevice : public Device
{
public:
// Initializer for DeviceRegistry.
static Result<void> create(SharedPtr<ATA::Drive> drive);
Result<usize> read(u8*, usize, usize) const override;
Result<usize> write(const u8*, usize, usize) override
{
return err(ENOTSUP);
}
bool blocking() const override
{
return false;
}
usize size() const override
{
return m_drive->capacity();
}
virtual ~ATADevice() = default;
private:
ATADevice() = default;
SharedPtr<ATA::Drive> m_drive;
};

View File

@ -84,6 +84,20 @@ INT(20);
INT(21);
INT(32);
INT(33);
INT(34);
INT(35);
INT(36);
INT(37);
INT(38);
INT(39);
INT(40);
INT(41);
INT(42);
INT(43);
INT(44);
INT(45);
INT(46);
INT(47);
INT(66);
void setup_idt()
@ -112,6 +126,20 @@ void setup_idt()
TRAP(21);
IRQ(32);
IRQ(33);
IRQ(34);
IRQ(35);
IRQ(36);
IRQ(37);
IRQ(38);
IRQ(39);
IRQ(40);
IRQ(41);
IRQ(42);
IRQ(43);
IRQ(44);
IRQ(45);
IRQ(46);
IRQ(47);
SYS(66);
static IDTR idtr;

View File

@ -36,9 +36,18 @@ void remap_pic()
IO::outb(PIC2_DATA, ICW4_8086);
io_delay();
IO::outb(PIC1_DATA, 0b11111100);
IO::outb(PIC1_DATA, 0b11111111);
io_delay();
IO::outb(PIC2_DATA, 0b11111111);
io_delay();
}
void change_pic_masks(u8 pic1_mask, u8 pic2_mask)
{
IO::outb(PIC1_DATA, pic1_mask);
io_delay();
IO::outb(PIC2_DATA, pic2_mask);
io_delay();
}
void pic_eoi(unsigned char irq)

View File

@ -15,28 +15,60 @@ namespace VFS
return *root_fs->root_inode();
}
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, SharedPtr<Inode> working_directory)
static constexpr int MAX_SYMLINKS = 8;
Result<SharedPtr<Inode>> resolve_path_impl(const char* path, Credentials auth, SharedPtr<Inode> current_inode,
bool follow_last_symlink, int& symlinks_followed)
{
if (symlinks_followed >= MAX_SYMLINKS) return err(ELOOP);
if (*path == '\0') return err(ENOENT);
auto parser = TRY(PathParser::create(path));
SharedPtr<Inode> current_inode;
if (parser.is_absolute() || !working_directory) current_inode = root_fs->root_inode();
else
current_inode = working_directory;
SharedPtr<Inode> parent_inode = current_inode;
const char* section;
while (parser.next().try_set_value(section))
{
if (!can_execute(current_inode, auth)) return err(EACCES);
current_inode = TRY(current_inode->find(section));
if (current_inode->type() == VFS::InodeType::Symlink && (follow_last_symlink || parser.has_next()))
{
auto link = TRY(current_inode->readlink());
SharedPtr<VFS::Inode> symlink_root;
if (PathParser::is_absolute(link.chars())) symlink_root = root_fs->root_inode();
else
symlink_root = parent_inode;
symlinks_followed++;
current_inode = TRY(resolve_path_impl(link.chars(), auth, symlink_root, true, symlinks_followed));
symlinks_followed--;
}
parent_inode = current_inode;
}
return current_inode;
}
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory,
bool follow_last_symlink)
{
SharedPtr<Inode> current_inode;
if (PathParser::is_absolute(path) || !working_directory) current_inode = root_fs->root_inode();
else
current_inode = working_directory;
int symlinks_followed = 0;
return resolve_path_impl(path, auth, current_inode, follow_last_symlink, symlinks_followed);
}
Result<SharedPtr<Inode>> create_directory(const char* path, Credentials auth, SharedPtr<Inode> working_directory)
{
auto parser = TRY(PathParser::create(path));

View File

@ -12,7 +12,8 @@ namespace VFS
{
RegularFile,
Directory,
Device
Device,
Symlink
};
class Inode;
@ -28,6 +29,8 @@ namespace VFS
virtual Result<SharedPtr<Inode>> create_device_inode(u32 major, u32 minor) = 0;
virtual Result<SharedPtr<Inode>> create_symlink_inode(StringView link) = 0;
virtual Result<void> set_mount_dir(SharedPtr<Inode> parent) = 0;
virtual u64 handles() const
@ -92,6 +95,12 @@ namespace VFS
virtual bool blocking() const = 0;
// Symlink-specific methods
virtual Result<StringView> readlink()
{
return StringView {};
}
// Metadata accessors
virtual usize size() const = 0;
@ -250,7 +259,8 @@ namespace VFS
};
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth,
SharedPtr<VFS::Inode> working_directory = {});
SharedPtr<VFS::Inode> working_directory = {},
bool follow_last_symlink = true);
Result<SharedPtr<Inode>> create_directory(const char* path, Credentials auth,
SharedPtr<VFS::Inode> working_directory = {});

View File

@ -12,6 +12,7 @@ namespace DeviceRegistry
Console = 1,
Memory = 2,
Framebuffer = 3,
Disk = 4,
};
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);

View File

@ -24,6 +24,15 @@ namespace TmpFS
return (SharedPtr<VFS::Inode>)inode;
}
Result<SharedPtr<VFS::Inode>> FileSystem::create_symlink_inode(StringView link)
{
SharedPtr<SymlinkInode> inode = TRY(make_shared<SymlinkInode>());
inode->set_fs(*this, {});
TRY(inode->set_link(link, {}));
inode->set_inode_number(m_next_inode_number++, {});
return (SharedPtr<VFS::Inode>)inode;
}
Result<SharedPtr<VFS::Inode>> FileSystem::create_dir_inode(SharedPtr<VFS::Inode> parent)
{
SharedPtr<DirInode> inode = TRY(make_shared<DirInode>());

View File

@ -5,6 +5,7 @@
#include <luna/Badge.h>
#include <luna/Buffer.h>
#include <luna/StaticString.h>
#include <luna/String.h>
#include <luna/Vector.h>
namespace TmpFS
@ -20,6 +21,7 @@ namespace TmpFS
Result<SharedPtr<VFS::Inode>> create_file_inode() override;
Result<SharedPtr<VFS::Inode>> create_dir_inode(SharedPtr<VFS::Inode> parent) override;
Result<SharedPtr<VFS::Inode>> create_device_inode(u32 major, u32 minor) override;
Result<SharedPtr<VFS::Inode>> create_symlink_inode(StringView link) override;
Result<void> set_mount_dir(SharedPtr<VFS::Inode> parent) override;
@ -120,6 +122,115 @@ namespace TmpFS
u32 m_nlinks { 0 };
};
class SymlinkInode : public VFS::FileInode
{
public:
SymlinkInode() = default;
VFS::InodeType type() const override
{
return VFS::InodeType::Symlink;
}
void set_fs(FileSystem& fs, Badge<FileSystem>)
{
m_fs = &fs;
}
void set_inode_number(usize inum, Badge<FileSystem>)
{
m_inode_number = inum;
}
Result<void> set_link(StringView link, Badge<FileSystem>)
{
m_link = TRY(String::from_string_view(link));
return {};
}
VFS::FileSystem* fs() const override
{
return m_fs;
}
usize inode_number() const override
{
return m_inode_number;
}
Result<usize> read(u8*, usize, usize) const override
{
return err(ENOTSUP);
}
Result<usize> write(const u8*, usize, usize) override
{
return err(ENOTSUP);
}
Result<void> truncate(usize) override
{
return err(ENOTSUP);
}
usize size() const override
{
return m_link.length();
}
mode_t mode() const override
{
return 0777;
}
u32 uid() const override
{
return m_uid;
}
u32 gid() const override
{
return m_gid;
}
Result<void> chmod(mode_t) override
{
return {};
}
Result<void> chown(u32 uid, u32 gid) override
{
m_uid = uid;
m_gid = gid;
return {};
}
void did_link() override
{
m_nlinks++;
}
void did_unlink() override
{
m_nlinks--;
}
Result<StringView> readlink() override
{
return m_link.view();
}
virtual ~SymlinkInode() = default;
private:
VFS::FileSystem* m_fs;
String m_link;
usize m_inode_number;
u32 m_uid { 0 };
u32 m_gid { 0 };
u32 m_nlinks { 0 };
};
class DeviceInode : public VFS::DeviceInode
{
public:

77
kernel/src/lib/KMutex.h Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#include "Log.h"
#include "arch/CPU.h"
#include "thread/Scheduler.h"
#include "thread/Thread.h"
#include <luna/CircularQueue.h>
template <usize ConcurrentThreads> class KMutex
{
public:
void lock()
{
int expected = 0;
while (!m_lock.compare_exchange_strong(expected, 1))
{
expected = 0;
auto* current = Scheduler::current();
// We cannot be interrupted between these functions, otherwise we might never exit the loop
CPU::disable_interrupts();
bool ok = m_blocked_threads.try_push(current);
if (!ok) kernel_sleep(10);
else
kernel_wait_for_event();
CPU::enable_interrupts();
}
};
void unlock()
{
int expected = 1;
if (!m_lock.compare_exchange_strong(expected, 0))
{
kwarnln("KMutex::unlock() called on an unlocked lock with value %d", expected);
}
Thread* blocked;
if (m_blocked_threads.try_pop(blocked)) blocked->wake_up();
}
bool try_lock()
{
int expected = 0;
return m_lock.compare_exchange_strong(expected, 1);
}
private:
CircularQueue<Thread*, ConcurrentThreads> m_blocked_threads;
Atomic<int> m_lock;
};
template <usize ConcurrentThreads> class ScopedKMutexLock
{
public:
ScopedKMutexLock(KMutex<ConcurrentThreads>& lock) : m_lock(lock)
{
m_lock.lock();
}
~ScopedKMutexLock()
{
if (!m_taken_over) m_lock.unlock();
}
ScopedKMutexLock(const ScopedKMutexLock&) = delete;
ScopedKMutexLock(ScopedKMutexLock&&) = delete;
KMutex<ConcurrentThreads>& take_over()
{
m_taken_over = true;
return m_lock;
}
private:
KMutex<ConcurrentThreads>& m_lock;
bool m_taken_over { false };
};

View File

@ -1,6 +1,5 @@
#include "Log.h"
#include "arch/CPU.h"
#include "arch/PCI.h"
#include "arch/Timer.h"
#include "boot/Init.h"
#include "config.h"
@ -11,6 +10,10 @@
#include "thread/Scheduler.h"
#include <luna/Units.h>
#ifdef ARCH_X86_64
#include "arch/x86_64/disk/ATA.h"
#endif
extern void set_host_name(StringView);
void reap_thread()
@ -53,12 +56,9 @@ Result<void> init()
auto reap = Scheduler::new_kernel_thread(reap_thread, "[reap]").release_value();
Scheduler::set_reap_thread(reap);
PCI::scan(
[](const PCI::Device& device) {
kinfoln("Found PCI mass storage device %.4x:%.4x, at address %u:%u:%u", device.id.vendor, device.id.device,
device.address.bus, device.address.slot, device.address.function);
},
{ .klass = 1 });
#ifdef ARCH_X86_64
ATA::Controller::scan();
#endif
// Disable console logging before transferring control to userspace.
setup_log(log_debug_enabled(), log_serial_enabled(), false);

View File

@ -104,7 +104,7 @@ Result<u64> sys_fchmodat(Registers*, SyscallArgs args)
auto* current = Scheduler::current();
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH));
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW)));
if (current->auth.euid != 0 && current->auth.euid != inode->uid()) return err(EPERM);
@ -123,7 +123,7 @@ Result<u64> sys_fchownat(Registers*, SyscallArgs args)
auto* current = Scheduler::current();
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH));
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW)));
if (current->auth.euid != 0) return err(EPERM);

View File

@ -21,7 +21,7 @@ Result<u64> sys_unlinkat(Registers*, SyscallArgs args)
kinfoln("unlinkat: remove %s from directory %s, dirfd is %d", basename.chars(), dirname.chars(), dirfd);
auto inode = TRY(current->resolve_atfile(dirfd, dirname, false));
auto inode = TRY(current->resolve_atfile(dirfd, dirname, false, false));
if (!VFS::can_write(inode, current->auth)) return err(EACCES);
if (flags > 0)
@ -34,3 +34,31 @@ Result<u64> sys_unlinkat(Registers*, SyscallArgs args)
return 0;
}
Result<u64> sys_symlinkat(Registers*, SyscallArgs args)
{
auto target = TRY(MemoryManager::strdup_from_user(args[0]));
int dirfd = (int)args[1];
auto linkpath = TRY(MemoryManager::strdup_from_user(args[2]));
if (target.is_empty()) return err(ENOENT);
auto* current = Scheduler::current();
auto parser = TRY(PathParser::create(linkpath.chars()));
auto parent = TRY(parser.dirname());
auto parent_inode = TRY(current->resolve_atfile(dirfd, parent, false, true));
if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES);
auto child_name = TRY(parser.basename());
TRY(VFS::validate_filename(child_name.view()));
auto inode = TRY(parent_inode->fs()->create_symlink_inode(target.view()));
TRY(inode->chown(current->auth.euid, current->auth.egid));
TRY(parent_inode->add_entry(inode, child_name.chars()));
return 0;
}

View File

@ -31,7 +31,8 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
int error;
SharedPtr<VFS::Inode> parent_inode;
bool ok = current->resolve_atfile(dirfd, path, false, &parent_inode).try_set_value_or_error(inode, error);
bool ok = current->resolve_atfile(dirfd, path, false, !(flags & O_NOFOLLOW), &parent_inode)
.try_set_value_or_error(inode, error);
if (!ok)
{
if (error == ENOENT && (flags & O_CREAT) && !path.is_empty())
@ -51,6 +52,9 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth)) return err(EACCES);
}
// This should only be possible if O_NOFOLLOW was in flags.
if (inode->type() == VFS::InodeType::Symlink) return err(ELOOP);
if (flags & O_TMPFILE)
{
if (inode->type() != VFS::InodeType::Directory) return err(EINVAL);

View File

@ -14,6 +14,7 @@ static mode_t make_mode(mode_t mode, VFS::InodeType type)
case VFS::InodeType::RegularFile: result |= S_IFREG; break;
case VFS::InodeType::Directory: result |= S_IFDIR; break;
case VFS::InodeType::Device: result |= S_IFCHR; break;
case VFS::InodeType::Symlink: result |= S_IFLNK; break;
default: break;
}
@ -29,7 +30,7 @@ Result<u64> sys_fstatat(Registers*, SyscallArgs args)
Thread* current = Scheduler::current();
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH));
auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW)));
stat kstat;

View File

@ -49,13 +49,15 @@ Result<FileDescriptor*> Thread::resolve_fd(int fd)
}
Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& path, bool allow_empty_path,
SharedPtr<VFS::Inode>* parent_inode)
bool follow_last_symlink, SharedPtr<VFS::Inode>* parent_inode)
{
if (parent_inode) *parent_inode = this->current_directory;
if (PathParser::is_absolute(path.view())) return VFS::resolve_path(path.chars(), this->auth);
if (PathParser::is_absolute(path.view()))
return VFS::resolve_path(path.chars(), this->auth, {}, follow_last_symlink);
if (dirfd == AT_FDCWD) return VFS::resolve_path(path.chars(), this->auth, this->current_directory);
if (dirfd == AT_FDCWD)
return VFS::resolve_path(path.chars(), this->auth, this->current_directory, follow_last_symlink);
auto descriptor = TRY(resolve_fd(dirfd));
@ -63,7 +65,7 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
if (path.is_empty() && allow_empty_path) return descriptor->inode;
return VFS::resolve_path(path.chars(), this->auth, descriptor->inode);
return VFS::resolve_path(path.chars(), this->auth, descriptor->inode, follow_last_symlink);
}
bool FileDescriptor::should_append()

View File

@ -76,6 +76,7 @@ struct Thread : public LinkedListNode<Thread>
Result<int> allocate_fd(int min);
Result<FileDescriptor*> resolve_fd(int fd);
Result<SharedPtr<VFS::Inode>> resolve_atfile(int dirfd, const String& path, bool allow_empty_path,
bool follow_last_symlink,
SharedPtr<VFS::Inode>* parent_inode = nullptr);
FPData fp_data;

View File

@ -6,6 +6,7 @@
#define AT_FDCWD -100
#define AT_EMPTY_PATH 1
#define AT_SYMLINK_NOFOLLOW 2
#define AT_REMOVEDIR 1

View File

@ -5,6 +5,7 @@
#define S_IFMT 070000
#define S_IFREG 000000
#define S_IFLNK 010000
#define S_IFDIR 040000
#define S_IFCHR 050000
@ -13,6 +14,7 @@
#define S_ISREG(mode) __CHECK_TYPE(mode, S_IFREG)
#define S_ISDIR(mode) __CHECK_TYPE(mode, S_IFDIR)
#define S_ISCHR(mode) __CHECK_TYPE(mode, S_IFCHR)
#define S_ISLNK(mode) __CHECK_TYPE(mode, S_IFLNK)
#define S_IRWXU 0700
#define S_IRUSR 0400

View File

@ -14,6 +14,7 @@
#define O_CLOEXEC 128
#define O_DIRECTORY 256
#define O_TMPFILE 512
#define O_NOFOLLOW 1024
#define O_ACCMODE O_RDWR

View File

@ -26,6 +26,8 @@ extern FILE* stderr;
#define stdout stdout
#define stderr stderr
#define BUFSIZ 1024
#ifdef __cplusplus
extern "C"
{
@ -96,6 +98,12 @@ extern "C"
/* Read a line from stream. */
char* fgets(char* buf, size_t size, FILE* stream);
/* Read a line from stream and store it in a dynamically-allocated buffer. */
ssize_t getline(char** linep, size_t* n, FILE* stream);
/* Read a line from stream and store it in a dynamically-allocated buffer. */
ssize_t getdelim(char** linep, size_t* n, int delim, FILE* stream);
/* Clear the error and end-of-file indicators in stream. */
void clearerr(FILE* stream);

View File

@ -71,6 +71,9 @@ extern "C"
/* Separate a string into several tokens. */
char* strtok(char* str, const char* delim);
/* Separate a string into several tokens. */
char* strtok_r(char* str, const char* delim, char** savep);
/* Return a heap-allocated copy of a fixed-size string. */
char* strndup(const char* str, size_t max);

View File

@ -29,6 +29,9 @@ extern "C"
#pragma GCC pop_options
/* Retrieve information about a file or symbolic link. */
int lstat(const char* path, struct stat* st);
/* Retrieve information about a file. */
int fstat(int fd, struct stat* st);

View File

@ -133,6 +133,12 @@ extern "C"
/* Create a pipe for inter-process communication. */
int pipe(int pfds[2]);
/* Create a symbolic link. */
int symlink(const char* target, const char* linkpath);
/* Create a symbolic link relative to a file descriptor. */
int symlinkat(const char* target, int dirfd, const char* linkpath);
#ifdef __cplusplus
}
#endif

View File

@ -236,6 +236,66 @@ extern "C"
return buf;
}
ssize_t getline(char** linep, size_t* n, FILE* stream)
{
return getdelim(linep, n, '\n', stream);
}
ssize_t getdelim(char** linep, size_t* n, int delim, FILE* stream)
{
if (!n || !linep)
{
errno = EINVAL;
return -1;
}
char* buf = *linep;
size_t size = *n;
size_t len = 0;
if (!buf)
{
buf = (char*)malloc(BUFSIZ);
size = BUFSIZ;
if (!buf) return -1;
*linep = buf;
*n = size;
}
while (1)
{
int c = fgetc(stream);
if (c == EOF) break;
if (len == size)
{
buf = (char*)realloc(buf, size + 64);
size += 64;
if (!buf) return -1;
*linep = buf;
*n = size;
}
buf[len++] = (char)c;
if (c == delim) break;
}
if (len == 0) return -1;
if (len == size)
{
buf = (char*)realloc(buf, size + 16);
size += 16;
if (!buf) return -1;
*linep = buf;
*n = size;
}
buf[len] = '\0';
return (ssize_t)len;
}
void clearerr(FILE* stream)
{
stream->_eof = stream->_err = 0;

View File

@ -36,7 +36,7 @@ static bool try_format(strftime_state& state, const char* format, ...)
va_list ap;
va_start(ap, format);
char buf[1024];
char buf[BUFSIZ];
vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap);

View File

@ -30,6 +30,12 @@ extern "C"
__errno_return(rc, int);
}
int lstat(const char* path, struct stat* st)
{
long rc = syscall(SYS_fstatat, AT_FDCWD, path, st, AT_SYMLINK_NOFOLLOW);
__errno_return(rc, int);
}
int fstat(int fd, struct stat* st)
{
long rc = syscall(SYS_fstatat, fd, "", st, AT_EMPTY_PATH);

View File

@ -4,6 +4,7 @@
#include <luna/Vector.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@ -347,17 +348,17 @@ extern "C"
}
else
{
buf = (char*)malloc(1024);
buf = (char*)malloc(BUFSIZ);
if (!buf) return nullptr;
ssize_t rc = __getcwd_wrapper(buf, 1024);
ssize_t rc = __getcwd_wrapper(buf, BUFSIZ);
if (rc < 0)
{
free(buf);
return nullptr;
}
if (rc > 1024)
if (rc > BUFSIZ)
{
free(buf);
@ -405,4 +406,16 @@ extern "C"
long rc = syscall(SYS_pipe, (int*)pfds);
__errno_return(rc, int);
}
int symlink(const char* target, const char* linkpath)
{
long rc = syscall(SYS_symlinkat, target, AT_FDCWD, linkpath);
__errno_return(rc, int);
}
int symlinkat(const char* target, int dirfd, const char* linkpath)
{
long rc = syscall(SYS_symlinkat, target, dirfd, linkpath);
__errno_return(rc, int);
}
}

View File

@ -18,6 +18,7 @@ extern "C"
usize strcspn(const char* str, const char* reject);
char* strtok(char* str, const char* delim);
char* strtok_r(char* str, const char* delim, char** savep);
usize wcslen(const wchar_t* str);
int wcscmp(const wchar_t* a, const wchar_t* b);

View File

@ -3,6 +3,11 @@
#include <luna/Heap.h>
#include <luna/String.h>
static inline bool is_not_delim(char c)
{
return c && c != '/';
}
class PathParser
{
public:
@ -27,6 +32,11 @@ class PathParser
return is_absolute(StringView { m_original });
}
bool has_next() const
{
return m_already_called_next ? (bool)m_strtok_saved_state : is_not_delim(*m_copy);
}
Result<String> basename();
Result<String> dirname();
@ -38,4 +48,5 @@ class PathParser
const char* m_original { nullptr };
char* m_copy { nullptr };
bool m_already_called_next { false };
char* m_strtok_saved_state { nullptr };
};

View File

@ -1,5 +1,6 @@
#pragma once
#include <luna/CString.h>
#include <luna/StringView.h>
#include <luna/Types.h>
template <usize Size> class StaticString
@ -24,12 +25,27 @@ template <usize Size> class StaticString
m_length = length;
}
void adopt(StringView string)
{
usize length = strlcpy(m_buffer, string.chars(),
string.length() > sizeof(m_buffer) ? sizeof(m_buffer) : string.length() + 1);
if (length > Size) { m_length = Size; }
else
m_length = length;
}
StaticString<Size>& operator=(const char* string)
{
adopt(string);
return *this;
}
StaticString<Size>& operator=(StringView string)
{
adopt(string);
return *this;
}
template <usize OtherSize> StaticString<Size>& operator=(const StaticString<OtherSize>& string)
{
if constexpr (OtherSize == Size)
@ -46,11 +62,38 @@ template <usize Size> class StaticString
return m_buffer;
}
char* data()
{
return m_buffer;
}
void set_length(usize len)
{
m_length = len;
}
usize length() const
{
return m_length;
}
void trim(StringView delim)
{
isize i = (isize)m_length;
while (i--)
{
char c = m_buffer[i];
if (!strchr(delim.chars(), c)) break;
}
i++;
m_buffer[i] = '\0';
m_length = (usize)i;
}
private:
char m_buffer[Size + 1];
usize m_length { 0 };

View File

@ -5,7 +5,7 @@
_e(lseek) _e(mkdir) _e(execve) _e(fork) _e(waitpid) _e(getppid) _e(fcntl) _e(getdents) _e(getuid) _e(geteuid) \
_e(getgid) _e(getegid) _e(setuid) _e(setgid) _e(seteuid) _e(setegid) _e(fchmodat) _e(fchownat) _e(ioctl) \
_e(fstatat) _e(chdir) _e(getcwd) _e(unlinkat) _e(uname) _e(sethostname) _e(dup2) _e(pipe) _e(mount) \
_e(umount) _e(pstat) _e(getrusage)
_e(umount) _e(pstat) _e(getrusage) _e(symlinkat)
enum Syscalls
{

View File

@ -219,9 +219,9 @@ extern "C"
return (usize)(s - str);
}
char* strtok(char* str, const char* delim)
char* strtok_r(char* str, const char* delim, char** savep)
{
static char* s = nullptr;
auto& s = *savep;
if (str) s = str;
if (!s) return nullptr;
@ -248,6 +248,12 @@ extern "C"
return nullptr;
}
char* strtok(char* str, const char* delim)
{
static char* s;
return strtok_r(str, delim, &s);
}
char* strpbrk(const char* s, const char* accept)
{
usize index = strcspn(s, accept);

View File

@ -27,7 +27,7 @@ PathParser::~PathParser()
Option<const char*> PathParser::next()
{
char* result = strtok(m_already_called_next ? nullptr : m_copy, "/");
char* result = strtok_r(m_already_called_next ? nullptr : m_copy, "/", &m_strtok_saved_state);
m_already_called_next = true;
if (!result) return {};

View File

@ -9,11 +9,11 @@ namespace os
{
namespace FileSystem
{
bool exists(const Path& path);
bool exists(const Path& path, bool follow_symlinks = true);
bool is_directory(const Path& path);
bool is_directory(const Path& path, bool follow_symlinks = false);
Result<void> stat(const Path& path, struct stat& st);
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks = true);
Result<void> create_directory(StringView path, mode_t mode);

View File

@ -12,28 +12,29 @@
namespace os::FileSystem
{
bool exists(const Path& path)
bool exists(const Path& path, bool follow_symlinks)
{
struct stat st;
if (stat(path, st).has_error()) return false;
if (stat(path, st, follow_symlinks).has_error()) return false;
return true;
}
bool is_directory(const Path& path)
bool is_directory(const Path& path, bool follow_symlinks)
{
struct stat st;
if (stat(path, st).has_error()) return false;
if (stat(path, st, follow_symlinks).has_error()) return false;
return S_ISDIR(st.st_mode);
}
Result<void> stat(const Path& path, struct stat& st)
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks)
{
long rc =
syscall(SYS_fstatat, path.dirfd(), path.name().chars(), &st, path.is_empty_path() ? AT_EMPTY_PATH : 0);
syscall(SYS_fstatat, path.dirfd(), path.name().chars(), &st,
(int)((path.is_empty_path() ? AT_EMPTY_PATH : 0) | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW)));
return Result<void>::from_syscall(rc);
}