Compare commits

..

15 Commits

Author SHA1 Message Date
Bananymous c50dba900d General: Update README features and environment variables 2025-07-17 21:32:53 +03:00
Bananymous 502bb4f84a BuildSystem: Add support for building initrd image
This is nice for testing when there isn't xhci controller available or
my usb stack fails :)
2025-07-17 21:25:59 +03:00
Bananymous d5301508ec Kernel: Increase kernel thread stack size
HACK HACK HACK

This is just to make banan-os boot on one razer laptop where AML
triggers a stack overflow :)
2025-07-17 21:21:14 +03:00
Bananymous 793cca423b Kernel: Fix system timer disabling
I was actually not disabling system timer (HPET, PIT) when using lapic
timers for scheduling. This made BSB get too many timer interrupts :D
2025-07-16 20:02:04 +03:00
Bananymous 3960687f9d Kernel: Parse PCIConfig opregion address on read/write
I was testing on some hardware and _ADR does not have to exist in the
namespace when opregion is parsed :)
2025-07-16 15:34:36 +03:00
Bananymous 3ec7aad432 Kernel: Increase PS/2 timeout and detect more keyboards
I was testing on a old T61
2025-07-16 15:29:27 +03:00
Bananymous 84f1ad4f26 ports: Fix doom compilation with the new toolchain 2025-07-15 16:12:03 +03:00
Bananymous 6b9dbf625d userspace: Add `audio` utility to play audio files 2025-07-15 16:12:03 +03:00
Bananymous 3aea2c007d Userspace: Add simple AudioServer and LibAudio 2025-07-15 16:12:03 +03:00
Bananymous 85d195212a BuildSystem: Update qemu script and add ac97 2025-07-15 14:17:49 +03:00
Bananymous 8a663cb94f Kernel: Implement basic AC97 driver 2025-07-15 14:17:49 +03:00
Bananymous 674e194a91 Kernel: Don't fail PCI interrupt allocation with PCIe and no PCI 2025-07-15 14:17:49 +03:00
Bananymous c57f0abb56 BuildSystem: Order source list alphabetically 2025-07-15 14:17:49 +03:00
Bananymous 163fdcd582 LibC: Fix exec family function path resolution 2025-07-15 14:17:49 +03:00
Bananymous 3be17c6117 BAN: Add clear and access by index to CircularQueue 2025-07-10 17:24:40 +03:00
40 changed files with 1665 additions and 94 deletions

View File

@ -34,6 +34,11 @@ namespace BAN
const T& back() const;
T& back();
const T& operator[](size_t index) const;
T& operator[](size_t index);
void clear();
size_type size() const { return m_size; }
bool empty() const { return size() == 0; }
bool full() const { return size() == capacity(); }
@ -53,8 +58,7 @@ namespace BAN
template<typename T, size_t S>
CircularQueue<T, S>::~CircularQueue()
{
for (size_type i = 0; i < m_size; i++)
element_at((m_first + i) % capacity())->~T();
clear();
}
template<typename T, size_t S>
@ -115,6 +119,28 @@ namespace BAN
return *element_at((m_first + m_size - 1) % capacity());
}
template<typename T, size_t S>
const T& CircularQueue<T, S>::operator[](size_t index) const
{
ASSERT(index < m_size);
return *element_at((m_first + index) % capacity());
}
template<typename T, size_t S>
T& CircularQueue<T, S>::operator[](size_t index)
{
ASSERT(index < m_size);
return *element_at((m_first + index) % capacity());
}
template<typename T, size_t S>
void CircularQueue<T, S>::clear()
{
for (size_type i = 0; i < m_size; i++)
element_at((m_first + i) % capacity())->~T();
m_size = 0;
}
template<typename T, size_t S>
const T* CircularQueue<T, S>::element_at(size_type index) const
{

View File

@ -23,7 +23,7 @@ If you want to try out DOOM, you should first enter the GUI environment using th
- [x] Basic graphical environment
- [x] Terminal emulator
- [x] Status bar
- [ ] Program launcher
- [x] Program launcher
- [ ] Some nice apps
- [x] ELF dynamic linking
- [x] copy-on-write memory
@ -118,6 +118,8 @@ To change the bootloader you can set environment variable BANAN\_BOOTLOADER; sup
To run with UEFI set environment variable BANAN\_UEFI\_BOOT=1. You will also have to set OVMF\_PATH to the correct OVMF (default */usr/share/ovmf/x64/OVMF.fd*).
To build an image with no physical root filesystem, but an initrd set environment variable BANAN\_INITRD=1. This can be used when testing on hardware with unsupported USB controller.
If you have corrupted your disk image or want to create new one, you can either manually delete *build/banan-os.img* and build system will automatically create you a new one or you can run the following command.
```sh
./bos image-full

View File

@ -1,11 +1,13 @@
set(KERNEL_SOURCES
font/prefs.psf.o
kernel/ACPI/ACPI.cpp
kernel/ACPI/BatterySystem.cpp
kernel/ACPI/AML/Namespace.cpp
kernel/ACPI/AML/Node.cpp
kernel/ACPI/AML/OpRegion.cpp
kernel/ACPI/BatterySystem.cpp
kernel/APIC.cpp
kernel/Audio/AC97/Controller.cpp
kernel/Audio/Controller.cpp
kernel/BootInfo.cpp
kernel/CPUID.cpp
kernel/Credentials.cpp
@ -75,7 +77,6 @@ set(KERNEL_SOURCES
kernel/Processor.cpp
kernel/Random.cpp
kernel/Scheduler.cpp
kernel/ThreadBlocker.cpp
kernel/SSP.cpp
kernel/Storage/ATA/AHCI/Controller.cpp
kernel/Storage/ATA/AHCI/Device.cpp
@ -98,6 +99,7 @@ set(KERNEL_SOURCES
kernel/Terminal/TTY.cpp
kernel/Terminal/VirtualTTY.cpp
kernel/Thread.cpp
kernel/ThreadBlocker.cpp
kernel/Timer/HPET.cpp
kernel/Timer/PIT.cpp
kernel/Timer/RTC.cpp

View File

@ -87,14 +87,11 @@ namespace Kernel::ACPI::AML
struct OpRegion
{
GAS::AddressSpaceID address_space;
uint16_t seg;
uint8_t bus;
uint8_t dev;
uint8_t func;
uint64_t offset;
uint64_t length;
alignas(Scope) uint8_t scope_storage[sizeof(Scope)];
Scope& scope() { return *reinterpret_cast<Scope*>(scope_storage); }
const Scope& scope() const { return *reinterpret_cast<const Scope*>(scope_storage); }
};
struct FieldUnit

View File

@ -0,0 +1,51 @@
#pragma once
#include <kernel/Audio/Controller.h>
#include <kernel/Memory/DMARegion.h>
namespace Kernel
{
class AC97AudioController : public AudioController, public Interruptable
{
public:
static BAN::ErrorOr<BAN::RefPtr<AC97AudioController>> create(PCI::Device& pci_device);
void handle_irq() override;
protected:
void handle_new_data() override;
uint32_t get_channels() const override { return 2; }
uint32_t get_sample_rate() const override { return 48000; }
private:
AC97AudioController(PCI::Device& pci_device)
: m_pci_device(pci_device)
{ }
BAN::ErrorOr<void> initialize();
BAN::ErrorOr<void> initialize_bld();
BAN::ErrorOr<void> initialize_interrupts();
void queue_samples_to_bld();
private:
static constexpr size_t m_bdl_entries = 32;
static constexpr size_t m_samples_per_entry = 0x1000;
// We only store samples in 2 BDL entries at a time to reduce the amount of samples queued.
// This is to reduce latency as you cannot remove data already passed to the BDLs
static constexpr size_t m_used_bdl_entries = 2;
PCI::Device& m_pci_device;
BAN::UniqPtr<PCI::BarRegion> m_mixer;
BAN::UniqPtr<PCI::BarRegion> m_bus_master;
BAN::UniqPtr<DMARegion> m_bdl_region;
uint32_t m_bdl_tail { 0 };
uint32_t m_bdl_head { 0 };
};
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
namespace Kernel::AC97
{
struct BufferDescriptorListEntry
{
uint32_t address;
uint16_t samples;
uint16_t flags; // bit 14: last entry, bit 15: IOC
};
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <kernel/Device/Device.h>
#include <kernel/PCI.h>
namespace Kernel
{
class AudioController : public CharacterDevice
{
public:
static BAN::ErrorOr<BAN::RefPtr<AudioController>> create(PCI::Device& pci_device);
dev_t rdev() const override { return m_rdev; }
BAN::StringView name() const override { return m_name; }
protected:
AudioController();
virtual void handle_new_data() = 0;
virtual uint32_t get_channels() const = 0;
virtual uint32_t get_sample_rate() const = 0;
bool can_read_impl() const override { return false; }
bool can_write_impl() const override { SpinLockGuard _(m_spinlock); return m_sample_data_size < m_sample_data_capacity; }
bool has_error_impl() const override { return false; }
bool has_hungup_impl() const override { return false; }
BAN::ErrorOr<size_t> write_impl(off_t, BAN::ConstByteSpan) override;
BAN::ErrorOr<long> ioctl_impl(int cmd, void* arg) override;
protected:
ThreadBlocker m_sample_data_blocker;
mutable SpinLock m_spinlock;
static constexpr size_t m_sample_data_capacity = 1 << 20;
uint8_t m_sample_data[m_sample_data_capacity];
size_t m_sample_data_head { 0 };
size_t m_sample_data_size { 0 };
private:
const dev_t m_rdev;
char m_name[10] {};
};
}

View File

@ -22,6 +22,7 @@ namespace Kernel
Ethernet,
Loopback,
TmpFS,
AudioController,
};
}

View File

@ -32,7 +32,9 @@ namespace Kernel
Terminated,
};
static constexpr size_t kernel_stack_size { PAGE_SIZE * 8 };
// FIXME: kernel stack does NOT have to be this big, but my recursive AML interpreter
// stack overflows on some machines with 8 page stack
static constexpr size_t kernel_stack_size { PAGE_SIZE * 16 };
static constexpr size_t userspace_stack_size { PAGE_SIZE * 128 };
public:

View File

@ -20,13 +20,12 @@ namespace Kernel
virtual bool pre_scheduler_sleep_needs_lock() const = 0;
virtual void pre_scheduler_sleep_ns(uint64_t) = 0;
void dont_invoke_scheduler() { m_should_invoke_scheduler = false; }
protected:
bool should_invoke_scheduler() const { return m_should_invoke_scheduler; }
private:
bool m_should_invoke_scheduler { true };
friend class SystemTimer;
};
class SystemTimer : public Timer
@ -46,6 +45,8 @@ namespace Kernel
void sleep_ms(uint64_t ms) const { ASSERT(!BAN::Math::will_multiplication_overflow<uint64_t>(ms, 1'000'000)); return sleep_ns(ms * 1'000'000); }
void sleep_ns(uint64_t ns) const;
void dont_invoke_scheduler() { m_timer->m_should_invoke_scheduler = false; }
timespec real_time() const;
private:

View File

@ -1,9 +1,6 @@
// FIXME: Rewrite aml interpreter to not be recursive.
// Not inlining TRYs drops our stack usage a ton...
#pragma GCC push_options
#pragma GCC optimize "no-inline"
#include <BAN/Errors.h>
#pragma GCC pop_options
#include <BAN/Assert.h>
#include <BAN/String.h>
@ -3074,7 +3071,7 @@ namespace Kernel::ACPI::AML
return ExecutionFlowResult {
.elem1 = ExecutionFlow::Normal,
.elem2 = BAN::Optional<Node>(),
};;
};
case AML::Byte::BreakOp:
dprintln_if(AML_DUMP_FUNCTION_CALLS, "parse_break_op");
context.aml_data = context.aml_data.slice(1);
@ -3090,14 +3087,12 @@ namespace Kernel::ACPI::AML
.elem2 = BAN::Optional<Node>(),
};
case AML::Byte::ReturnOp:
{
dprintln_if(AML_DUMP_FUNCTION_CALLS, "parse_return_op");
context.aml_data = context.aml_data.slice(1);
return ExecutionFlowResult {
.elem1 = ExecutionFlow::Return,
.elem2 = TRY(parse_node(context)),
};
}
default:
break;
}
@ -3109,13 +3104,12 @@ namespace Kernel::ACPI::AML
return ExecutionFlowResult {
.elem1 = ExecutionFlow::Normal,
.elem2 = BAN::Optional<Node>(),
};;
};
}
auto node = TRY(parse_node(context));
return ExecutionFlowResult {
.elem1 = ExecutionFlow::Normal,
.elem2 = BAN::move(node)
.elem2 = TRY(parse_node(context)),
};
}
@ -3260,6 +3254,8 @@ namespace Kernel::ACPI::AML
break;
case Type::OpRegion:
result.as.opregion = this->as.opregion;
new (&result.as.opregion.scope()) Scope();
result.as.opregion.scope() = TRY(this->as.opregion.scope().copy());
break;
case Type::FieldUnit:
result.as.field_unit = this->as.field_unit;
@ -3364,7 +3360,9 @@ namespace Kernel::ACPI::AML
break;
case Type::OpRegion:
this->as.opregion = other.as.opregion;
other.as.opregion = {};
new (&this->as.opregion.scope()) Scope();
this->as.opregion.scope() = BAN::move(other.as.opregion.scope());
other.as.opregion.scope().~Scope();
break;
case Type::FieldUnit:
this->as.field_unit = other.as.field_unit;
@ -3457,6 +3455,7 @@ namespace Kernel::ACPI::AML
this->as.buffer_field = {};
break;
case Type::OpRegion:
this->as.opregion.scope().~Scope();
this->as.opregion = {};
break;
case Type::FieldUnit:

View File

@ -98,38 +98,8 @@ namespace Kernel::ACPI::AML
opregion.as.opregion.offset = region_offset.as.integer.value;
opregion.as.opregion.length = region_length.as.integer.value;
opregion.as.opregion.seg = 0;
opregion.as.opregion.bus = 0;
opregion.as.opregion.dev = 0;
opregion.as.opregion.func = 0;
if (opregion.as.opregion.address_space == GAS::AddressSpaceID::PCIConfig)
{
// FIXME: Am I actually allowed to read these here or should I determine
// them on every read/write access
if (auto seg_res = TRY(Namespace::root_namespace().find_named_object(context.scope, TRY(AML::NameString::from_string("_SEG"_sv)))); seg_res.node != nullptr)
{
auto seg_node = TRY(convert_node(TRY(evaluate_node(seg_res.path, seg_res.node->node)), ConvInteger, -1));
opregion.as.opregion.seg = seg_node.as.integer.value;
}
if (auto bbn_res = TRY(Namespace::root_namespace().find_named_object(context.scope, TRY(AML::NameString::from_string("_BBN"_sv)))); bbn_res.node != nullptr)
{
auto bbn_node = TRY(convert_node(TRY(evaluate_node(bbn_res.path, bbn_res.node->node)), ConvInteger, -1));
opregion.as.opregion.bus = bbn_node.as.integer.value;
}
auto adr_res = TRY(Namespace::root_namespace().find_named_object(context.scope, TRY(AML::NameString::from_string("_ADR"_sv))));
if (adr_res.node == nullptr)
{
dwarnln("No _ADR for PCIConfig OpRegion");
return BAN::Error::from_errno(EFAULT);
}
auto adr_node = TRY(convert_node(TRY(evaluate_node(adr_res.path, adr_res.node->node)), ConvInteger, -1));
opregion.as.opregion.dev = adr_node.as.integer.value >> 16;
opregion.as.opregion.func = adr_node.as.integer.value & 0xFF;
}
new (&opregion.as.opregion.scope()) Scope();
opregion.as.opregion.scope() = TRY(context.scope.copy());
TRY(Namespace::root_namespace().add_named_object(context, region_name, BAN::move(opregion)));
@ -414,6 +384,32 @@ namespace Kernel::ACPI::AML
return rule;
}
static BAN::ErrorOr<void> get_pci_config_address(const OpRegion& opregion, uint16_t& seg, uint8_t& bus, uint8_t& dev, uint8_t& func)
{
ASSERT(opregion.address_space == GAS::AddressSpaceID::PCIConfig);
seg = 0;
if (auto seg_res = TRY(Namespace::root_namespace().find_named_object(opregion.scope(), TRY(AML::NameString::from_string("_SEG"_sv)))); seg_res.node != nullptr)
seg = TRY(convert_node(TRY(evaluate_node(seg_res.path, seg_res.node->node)), ConvInteger, -1)).as.integer.value;
bus = 0;
if (auto bbn_res = TRY(Namespace::root_namespace().find_named_object(opregion.scope(), TRY(AML::NameString::from_string("_BBN"_sv)))); bbn_res.node != nullptr)
bus = TRY(convert_node(TRY(evaluate_node(bbn_res.path, bbn_res.node->node)), ConvInteger, -1)).as.integer.value;
auto adr_res = TRY(Namespace::root_namespace().find_named_object(opregion.scope(), TRY(AML::NameString::from_string("_ADR"_sv))));
if (adr_res.node == nullptr)
{
dwarnln("No _ADR for PCIConfig OpRegion");
return BAN::Error::from_errno(EFAULT);
}
auto adr_node = TRY(convert_node(TRY(evaluate_node(adr_res.path, adr_res.node->node)), ConvInteger, -1));
dev = adr_node.as.integer.value >> 16;
func = adr_node.as.integer.value & 0xFF;
return {};
}
static BAN::ErrorOr<uint64_t> perform_opregion_read(const OpRegion& opregion, uint8_t access_size, uint64_t offset)
{
ASSERT(offset % access_size == 0);
@ -455,7 +451,11 @@ namespace Kernel::ACPI::AML
ASSERT_NOT_REACHED();
case GAS::AddressSpaceID::PCIConfig:
{
if (opregion.seg != 0)
uint16_t seg;
uint8_t bus, dev, func;
TRY(get_pci_config_address(opregion, seg, bus, dev, func));
if (seg != 0)
{
dwarnln("PCIConfig OpRegion with segment");
return BAN::Error::from_errno(ENOTSUP);
@ -463,11 +463,11 @@ namespace Kernel::ACPI::AML
switch (access_size)
{
case 1: return PCI::PCIManager::get().read_config_byte (opregion.bus, opregion.dev, opregion.func, byte_offset);
case 2: return PCI::PCIManager::get().read_config_word (opregion.bus, opregion.dev, opregion.func, byte_offset);
case 4: return PCI::PCIManager::get().read_config_dword(opregion.bus, opregion.dev, opregion.func, byte_offset);
case 1: return PCI::PCIManager::get().read_config_byte (bus, dev, func, byte_offset);
case 2: return PCI::PCIManager::get().read_config_word (bus, dev, func, byte_offset);
case 4: return PCI::PCIManager::get().read_config_dword(bus, dev, func, byte_offset);
default:
dwarnln("{} byte read from PCI {2H}:{2H}:{2H} offset {2H}", access_size, opregion.bus, opregion.dev, opregion.func, byte_offset);
dwarnln("{} byte read from PCI {2H}:{2H}:{2H} offset {2H}", access_size, bus, dev, func, byte_offset);
return BAN::Error::from_errno(EINVAL);
}
ASSERT_NOT_REACHED();
@ -525,7 +525,11 @@ namespace Kernel::ACPI::AML
return {};
case GAS::AddressSpaceID::PCIConfig:
{
if (opregion.seg != 0)
uint16_t seg;
uint8_t bus, dev, func;
TRY(get_pci_config_address(opregion, seg, bus, dev, func));
if (seg != 0)
{
dwarnln("PCIConfig OpRegion with segment");
return BAN::Error::from_errno(ENOTSUP);
@ -533,11 +537,11 @@ namespace Kernel::ACPI::AML
switch (access_size)
{
case 1: PCI::PCIManager::get().write_config_byte (opregion.bus, opregion.dev, opregion.func, byte_offset, value); break;
case 2: PCI::PCIManager::get().write_config_word (opregion.bus, opregion.dev, opregion.func, byte_offset, value); break;
case 4: PCI::PCIManager::get().write_config_dword(opregion.bus, opregion.dev, opregion.func, byte_offset, value); break;
case 1: PCI::PCIManager::get().write_config_byte (bus, dev, func, byte_offset, value); break;
case 2: PCI::PCIManager::get().write_config_word (bus, dev, func, byte_offset, value); break;
case 4: PCI::PCIManager::get().write_config_dword(bus, dev, func, byte_offset, value); break;
default:
dwarnln("{} byte write to PCI {2H}:{2H}:{2H} offset {2H}", access_size, opregion.bus, opregion.dev, opregion.func, byte_offset);
dwarnln("{} byte write to PCI {2H}:{2H}:{2H} offset {2H}", access_size, bus, dev, func, byte_offset);
return BAN::Error::from_errno(EINVAL);
}
return {};

View File

@ -0,0 +1,265 @@
#include <kernel/Audio/AC97/Controller.h>
#include <kernel/Audio/AC97/Definitions.h>
namespace Kernel
{
enum AudioMixerRegister : uint8_t
{
Reset = 0x00,
MasterVolume = 0x02,
AuxOutVolume = 0x04,
MonoVolume = 0x06,
MasterToneRL = 0x08,
PC_BEEPVolume = 0x0A,
PhoneVolume = 0x0C,
MicVolume = 0x0E,
LineInVolume = 0x10,
CDVolume = 0x12,
VideoVolume = 0x14,
AuxInVolume = 0x16,
PCMOutVolume = 0x18,
RecordSelect = 0x1A,
RecordGain = 0x1C,
RecordGainMic = 0x1E,
GeneralPurpose = 0x20,
_3DControl = 0x22,
PowerdownCtrlStat = 0x26,
ExtendedAudio = 0x28,
ExtendedAudioCtrlStat = 0x2A,
PCMFrontDACRate = 0x2C,
PCMSurroundDACRate = 0x2E,
PCMLFEDACRate = 0x30,
PCMLRADCRate = 0x32,
MICADCRate = 0x34,
_6ChVolC_LFE = 0x36,
_6ChVolL_R_Surround = 0x38,
S_PDIFControl = 0x3A
};
enum BusMasterRegister : uint8_t
{
PI_BDBAR = 0x00,
PI_CIV = 0x04,
PI_LVI = 0x05,
PI_SR = 0x06,
PI_PICB = 0x08,
PI_PIV = 0x0A,
PI_CR = 0x0B,
PO_BDBAR = 0x10,
PO_CIV = 0x14,
PO_LVI = 0x15,
PO_SR = 0x16,
PO_PICB = 0x18,
PO_PIV = 0x1A,
PO_CR = 0x1B,
MC_BDBAR = 0x20,
MC_CIV = 0x24,
MC_LVI = 0x25,
MC_SR = 0x26,
MC_PICB = 0x28,
MC_PIV = 0x2A,
MC_CR = 0x2B,
GLOB_CNT = 0x2C,
GLOB_STA = 0x30,
CAS = 0x34,
MC2_BDBAR = 0x40,
MC2_CIV = 0x44,
MC2_LVI = 0x45,
MC2_SR = 0x46,
MC2_PICB = 0x48,
MC2_PIV = 0x4A,
MC2_CR = 0x4B,
PI2_BDBAR = 0x50,
PI2_CIV = 0x54,
PI2_LVI = 0x55,
PI2_SR = 0x56,
PI2_PICB = 0x58,
PI2_PIV = 0x5A,
PI2_CR = 0x5B,
SPBAR = 0x60,
SPCIV = 0x64,
SPLVI = 0x65,
SPSR = 0x66,
SPPICB = 0x68,
SPPIV = 0x6A,
SPCR = 0x6B,
SDM = 0x80,
};
enum BusMasterStatus : uint16_t
{
DMAHalted = 1 << 0,
CELV = 1 << 1,
LVBCI = 1 << 2,
BCIS = 1 << 3,
FIFOE = 1 << 4,
};
enum BusMasterControl : uint8_t
{
RDBM = 1 << 0,
RR = 1 << 1,
LVBIE = 1 << 2,
FEIE = 1 << 3,
IOCE = 1 << 4,
};
BAN::ErrorOr<BAN::RefPtr<AC97AudioController>> AC97AudioController::create(PCI::Device& pci_device)
{
auto* ac97_ptr = new AC97AudioController(pci_device);
if (ac97_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto ac97 = BAN::RefPtr<AC97AudioController>::adopt(ac97_ptr);
TRY(ac97->initialize());
return ac97;
}
BAN::ErrorOr<void> AC97AudioController::initialize()
{
m_pci_device.enable_bus_mastering();
m_mixer = TRY(m_pci_device.allocate_bar_region(0));
m_bus_master = TRY(m_pci_device.allocate_bar_region(1));
if (m_mixer->size() < 0x34 || m_bus_master->size() < 0x34)
return BAN::Error::from_errno(ENODEV);
// Reset bus master
m_bus_master->write32(BusMasterRegister::GLOB_CNT, 0x00);
// no interrupts, 2 channels, 16 bit samples
m_bus_master->write32(BusMasterRegister::GLOB_CNT, 0x02);
// Reset mixer to default values
m_mixer->write16(AudioMixerRegister::Reset, 0);
// Master volume 100%, no mute
m_mixer->write16(AudioMixerRegister::MasterVolume, 0x0000);
// PCM output volume left/right +0 db, no mute
m_mixer->write16(AudioMixerRegister::PCMOutVolume, 0x0808);
TRY(initialize_bld());
TRY(initialize_interrupts());
// disable transfer, enable all interrupts
m_bus_master->write8(BusMasterRegister::PO_CR, IOCE | FEIE | LVBIE);
return {};
}
BAN::ErrorOr<void> AC97AudioController::initialize_bld()
{
const size_t bdl_size = sizeof(AC97::BufferDescriptorListEntry) * m_bdl_entries;
const size_t buffer_size = m_samples_per_entry * sizeof(int16_t);
m_bdl_region = TRY(DMARegion::create(bdl_size + buffer_size * m_used_bdl_entries));
memset(reinterpret_cast<void*>(m_bdl_region->vaddr()), 0x00, m_bdl_region->size());
for (size_t i = 0; i < m_bdl_entries; i++)
{
auto& entry = reinterpret_cast<AC97::BufferDescriptorListEntry*>(m_bdl_region->vaddr())[i];
entry.address = m_bdl_region->paddr() + bdl_size + (i % m_used_bdl_entries) * buffer_size;
entry.samples = 0;
entry.flags = 0;
}
m_bus_master->write32(BusMasterRegister::PO_BDBAR, m_bdl_region->paddr());
return {};
}
BAN::ErrorOr<void> AC97AudioController::initialize_interrupts()
{
TRY(m_pci_device.reserve_interrupts(1));
m_pci_device.enable_interrupt(0, *this);
// enable interrupts
m_bus_master->write32(BusMasterRegister::GLOB_CNT, m_bus_master->read32(BusMasterRegister::GLOB_CNT) | 0x01);
return {};
}
void AC97AudioController::handle_new_data()
{
ASSERT(m_spinlock.current_processor_has_lock());
if (m_bdl_head != m_bdl_tail)
return;
queue_samples_to_bld();
}
void AC97AudioController::queue_samples_to_bld()
{
ASSERT(m_spinlock.current_processor_has_lock());
uint32_t lvi = m_bdl_head;
while (m_bdl_head != (m_bdl_tail + m_used_bdl_entries) % m_bdl_entries)
{
const uint32_t next_bld_head = (m_bdl_head + 1) % m_bdl_entries;
if (next_bld_head == m_bdl_tail)
break;
const size_t sample_data_tail = (m_sample_data_head + m_sample_data_capacity - m_sample_data_size) % m_sample_data_capacity;
const size_t max_memcpy = BAN::Math::min(m_sample_data_size, m_sample_data_capacity - sample_data_tail);
const size_t samples = BAN::Math::min(max_memcpy / 2, m_samples_per_entry);
if (samples == 0)
break;
auto& entry = reinterpret_cast<AC97::BufferDescriptorListEntry*>(m_bdl_region->vaddr())[m_bdl_head];
entry.samples = samples;
entry.flags = (1 << 15);
memcpy(
reinterpret_cast<void*>(m_bdl_region->paddr_to_vaddr(entry.address)),
&m_sample_data[sample_data_tail],
samples * 2
);
m_sample_data_size -= samples * 2;
lvi = m_bdl_head;
m_bdl_head = next_bld_head;
}
// if head was not updated, no data was queued
if (lvi == m_bdl_head)
return;
m_sample_data_blocker.unblock();
m_bus_master->write8(BusMasterRegister::PO_LVI, lvi);
// start playing if we are not already
const uint8_t control = m_bus_master->read8(BusMasterRegister::PO_CR);
if (!(control & RDBM))
m_bus_master->write8(BusMasterRegister::PO_CR, control | RDBM);
}
void AC97AudioController::handle_irq()
{
const uint16_t status = m_bus_master->read16(BusMasterRegister::PO_SR);
if (!(status & (LVBCI | BCIS | FIFOE)))
return;
m_bus_master->write16(BusMasterRegister::PO_SR, LVBCI | BCIS | FIFOE);
SpinLockGuard _(m_spinlock);
if (status & LVBCI)
{
const uint8_t control = m_bus_master->read8(BusMasterRegister::PO_CR);
m_bus_master->write8(BusMasterRegister::PO_CR, control & ~RDBM);
}
if (status & BCIS)
{
m_bdl_tail = (m_bdl_tail + 1) % m_bdl_entries;
queue_samples_to_bld();
}
}
}

View File

@ -0,0 +1,105 @@
#include <kernel/Audio/AC97/Controller.h>
#include <kernel/Audio/Controller.h>
#include <kernel/Device/DeviceNumbers.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/Lock/SpinLockAsMutex.h>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>
namespace Kernel
{
static BAN::Atomic<dev_t> s_next_audio_minor = 0;
AudioController::AudioController()
: CharacterDevice(0644, 0, 0)
, m_rdev(makedev(DeviceNumber::AudioController, s_next_audio_minor++))
{
char* ptr = m_name;
BAN::Formatter::print([&ptr](char c) { *ptr++ = c; }, "audio{}", minor(m_rdev));
}
BAN::ErrorOr<BAN::RefPtr<AudioController>> AudioController::create(PCI::Device& pci_device)
{
switch (pci_device.subclass())
{
case 0x01:
// We should confirm that the card is actually AC97 but I'm trusting osdev wiki on this one
// > you can probably expect that every sound card with subclass 0x01 is sound card compatibile with AC97
if (auto ret = AC97AudioController::create(pci_device); !ret.is_error())
{
DevFileSystem::get().add_device(ret.value());
return BAN::RefPtr<AudioController>(ret.release_value());
}
else
{
dwarnln("Failed to initialize AC97: {}", ret.error());
return ret.release_error();
}
default:
dprintln("Unsupported Sound card (PCI {2H}:{2H}:{2H})",
pci_device.class_code(),
pci_device.subclass(),
pci_device.prog_if()
);
return BAN::Error::from_errno(ENOTSUP);
}
}
BAN::ErrorOr<size_t> AudioController::write_impl(off_t, BAN::ConstByteSpan buffer)
{
SpinLockGuard lock_guard(m_spinlock);
while (m_sample_data_size >= m_sample_data_capacity)
{
SpinLockGuardAsMutex smutex(lock_guard);
TRY(Thread::current().block_or_eintr_indefinite(m_sample_data_blocker, &smutex));
}
size_t nwritten = 0;
while (nwritten < buffer.size())
{
if (m_sample_data_size >= m_sample_data_capacity)
break;
const size_t max_memcpy = BAN::Math::min(m_sample_data_capacity - m_sample_data_size, m_sample_data_capacity - m_sample_data_head);
const size_t to_copy = BAN::Math::min(buffer.size() - nwritten, max_memcpy);
memcpy(m_sample_data + m_sample_data_head, buffer.data() + nwritten, to_copy);
nwritten += to_copy;
m_sample_data_head = (m_sample_data_head + to_copy) % m_sample_data_capacity;
m_sample_data_size += to_copy;
}
handle_new_data();
return nwritten;
}
BAN::ErrorOr<long> AudioController::ioctl_impl(int cmd, void* arg)
{
switch (cmd)
{
case SND_GET_CHANNELS:
*static_cast<uint32_t*>(arg) = get_channels();
return 0;
case SND_GET_SAMPLE_RATE:
*static_cast<uint32_t*>(arg) = get_sample_rate();
return 0;
case SND_RESET_BUFFER:
case SND_GET_BUFFERSZ:
{
SpinLockGuard _(m_spinlock);
*static_cast<uint32_t*>(arg) = m_sample_data_size;
if (cmd == SND_RESET_BUFFER)
m_sample_data_size = 0;
return 0;
}
}
return CharacterDevice::ioctl_impl(cmd, arg);
}
}

View File

@ -12,7 +12,7 @@
namespace Kernel::Input
{
static constexpr uint64_t s_ps2_timeout_ms = 300;
static constexpr uint64_t s_ps2_timeout_ms = 1000;
static PS2Controller* s_instance = nullptr;
@ -461,11 +461,20 @@ namespace Kernel::Input
}
// MF2 Keyboard
if (index == 2 && (bytes[0] == 0xAB && (bytes[1] == 0x83 || bytes[1] == 0x41)))
if (index == 2 && bytes[0] == 0xAB)
{
dprintln_if(DEBUG_PS2, "PS/2 found keyboard");
m_devices[device] = TRY(PS2Keyboard::create(*this, scancode_set));
return {};
switch (bytes[1])
{
case 0x41: // MF2 Keyboard (translated but my laptop uses this :))
case 0x83: // MF2 Keyboard
case 0xC1: // MF2 Keyboard
case 0x84: // Thinkpad KB
dprintln_if(DEBUG_PS2, "PS/2 found keyboard");
m_devices[device] = TRY(PS2Keyboard::create(*this, scancode_set));
return {};
default:
break;
}
}
dprintln_if(DEBUG_PS2, "PS/2 unsupported device {2H} {2H} ({} bytes) on port {}", bytes[0], bytes[1], index, device);

View File

@ -1,7 +1,8 @@
#include <BAN/ScopeGuard.h>
#include <kernel/APIC.h>
#include <kernel/ACPI/ACPI.h>
#include <kernel/APIC.h>
#include <kernel/Audio/Controller.h>
#include <kernel/IDT.h>
#include <kernel/IO.h>
#include <kernel/Memory/PageTable.h>
@ -228,6 +229,18 @@ namespace Kernel::PCI
dprintln("{}", res.error());
break;
}
case 0x04:
{
switch (pci_device.subclass())
{
case 0x01:
case 0x03:
if (auto res = AudioController::create(pci_device); res.is_error())
dprintln("Sound Card: {}", res.error());
break;
}
break;
}
case 0x0C:
{
switch (pci_device.subclass())
@ -636,7 +649,10 @@ namespace Kernel::PCI
for (const auto eisa_id : pci_root_bus_ids)
{
auto root_buses = TRY(acpi_namespace.find_device_with_eisa_id(eisa_id));
auto root_buses_or_error = acpi_namespace.find_device_with_eisa_id(eisa_id);
if (root_buses_or_error.is_error())
continue;
auto root_buses = root_buses_or_error.release_value();
for (const auto& root_bus : root_buses)
{

View File

@ -29,9 +29,9 @@ index 0000000..0878148
+ VB=@
+endif
+
+CC=$(BANAN_ARCH)-banan_os-gcc
+CXX=$(BANAN_ARCH)-banan_os-g++
+CFLAGS+=-O3 -Wall -DNORMALUNIX -DLINUX -DSNDSERV -D_DEFAULT_SOURCE
+CC=$(BANAN_ARCH)-pc-banan_os-gcc
+CXX=$(BANAN_ARCH)-pc-banan_os-g++
+CFLAGS+=-O3 -std=c11 -Wall -DNORMALUNIX -DLINUX -DSNDSERV -D_DEFAULT_SOURCE
+CXXFLAGS+=$(CFLAGS) --std=c++20
+LDFLAGS+=
+LIBS+=-lgui -linput -lstdc++

View File

@ -33,9 +33,15 @@ if [ ! -b $ROOT_PARTITION ]; then
fi
if sudo mount $ROOT_PARTITION $MOUNT_DIR; then
cd $MOUNT_DIR
sudo tar xf $BANAN_SYSROOT_TAR
cd
if (($BANAN_INITRD)); then
sudo mkdir -p $MOUNT_DIR/boot
sudo cp $BANAN_BUILD_DIR/kernel/banan-os.kernel $MOUNT_DIR/boot/banan-os.kernel
sudo cp $BANAN_SYSROOT_TAR $MOUNT_DIR/boot/banan-os.initrd
else
cd $MOUNT_DIR
sudo tar xf $BANAN_SYSROOT_TAR
cd
fi
sudo umount $MOUNT_DIR
fi

View File

@ -44,7 +44,11 @@ install_grub_legacy() {
--boot-directory="$mount_dir/boot" \
$loop_dev
sudo mkdir -p "$mount_dir/boot/grub"
sudo cp "$BANAN_BUILD_DIR/grub-legacy-boot.cfg" "$mount_dir/boot/grub/grub.cfg"
if (($BANAN_INITRD)); then
sudo cp "$BANAN_BUILD_DIR/grub-legacy-initrd.cfg" "$mount_dir/boot/grub/grub.cfg"
else
sudo cp "$BANAN_BUILD_DIR/grub-legacy.cfg" "$mount_dir/boot/grub/grub.cfg"
fi
sudo umount "$mount_dir"
}
@ -62,6 +66,11 @@ install_grub_uefi() {
}
install_banan_legacy() {
if (($BANAN_INITRD)); then
echo "banan bootloader does not support initrd" >&2
exit 1
fi
root_disk_info=$(fdisk -x "$BANAN_DISK_IMAGE_PATH" | tr -s ' ')
root_part_guid=$(echo "$root_disk_info" | grep "^$BANAN_DISK_IMAGE_PATH" | head -2 | tail -1 | cut -d' ' -f6)

View File

@ -22,19 +22,25 @@ elif [[ $BANAN_DISK_TYPE == "USB" ]]; then
else
DISK_ARGS="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0"
fi
DISK_ARGS="-drive format=raw,id=disk,file=${BANAN_DISK_IMAGE_PATH},if=none $DISK_ARGS"
QEMU_ARCH=$BANAN_ARCH
if [ $BANAN_ARCH = "i686" ]; then
QEMU_ARCH=i386
fi
qemu-system-$QEMU_ARCH \
-m 1G \
-smp 4 \
$BIOS_ARGS \
-drive format=raw,id=disk,file=${BANAN_DISK_IMAGE_PATH},if=none \
-device e1000e,netdev=net \
-netdev user,id=net \
-device qemu-xhci -device usb-kbd -device usb-mouse \
$DISK_ARGS \
$@ \
NET_ARGS='-netdev user,id=net'
NET_ARGS="-device e1000e,netdev=net $NET_ARGS"
USB_ARGS='-device qemu-xhci -device usb-kbd,port=1 -device usb-hub,port=2 -device usb-mouse,port=2.1'
SOUND_ARGS='-device ac97'
qemu-system-$QEMU_ARCH \
-m 1G -smp 4 \
$BIOS_ARGS \
$DISK_ARGS \
$USB_ARGS \
$NET_ARGS \
$SOUND_ARGS \
$@ \

View File

@ -0,0 +1,4 @@
menuentry "banan-os" {
multiboot2 /boot/banan-os.kernel readonly
module2 /boot/banan-os.initrd
}

View File

@ -0,0 +1,11 @@
insmod part_gpt
insmod search_fs_uuid
search --no-floppy --fs-uuid --set=root <ROOT_FS>
insmod all_video
menuentry "banan-os" {
multiboot2 /boot/banan-os.kernel readonly
module2 /boot/banan-os.initrd
}

View File

@ -1,4 +1,5 @@
set(USERSPACE_LIBRARIES
LibAudio
LibC
LibELF
LibFont

View File

@ -0,0 +1,136 @@
#include <BAN/ScopeGuard.h>
#include <LibAudio/Audio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
namespace LibAudio
{
BAN::ErrorOr<Audio> Audio::load(BAN::StringView path)
{
Audio result(TRY(AudioLoader::load(path)));
TRY(result.initialize(256 * 1024));
return result;
}
BAN::ErrorOr<Audio> Audio::random(uint32_t samples)
{
Audio result;
TRY(result.initialize(samples));
result.m_audio_buffer->sample_rate = 48000;
result.m_audio_buffer->channels = 1;
for (size_t i = 0; i < samples - 1; i++)
result.m_audio_buffer->samples[i] = (rand() - RAND_MAX / 2) / (RAND_MAX / 2.0);
result.m_audio_buffer->head = samples - 1;
return result;
}
void Audio::clear()
{
if (m_audio_buffer)
munmap(m_audio_buffer, m_smo_size);
m_audio_buffer = nullptr;
if (m_smo_key != -1)
smo_delete(m_smo_key);
m_smo_key = -1;
if (m_server_fd != -1)
close(m_server_fd);
m_server_fd = -1;
m_audio_loader.clear();
}
Audio& Audio::operator=(Audio&& other)
{
clear();
m_server_fd = other.m_server_fd;
m_smo_key = other.m_smo_key;
m_smo_size = other.m_smo_size;
m_audio_buffer = other.m_audio_buffer;
m_audio_loader = BAN::move(other.m_audio_loader);
other.m_server_fd = -1;
other.m_smo_key = -1;
other.m_smo_size = 0;
other.m_audio_buffer = nullptr;
return *this;
}
BAN::ErrorOr<void> Audio::start()
{
ASSERT(m_server_fd != -1);
const ssize_t nsend = send(m_server_fd, &m_smo_key, sizeof(m_smo_key), 0);
if (nsend == -1)
return BAN::Error::from_errno(errno);
ASSERT(nsend == sizeof(m_smo_key));
return {};
}
BAN::ErrorOr<void> Audio::initialize(uint32_t total_samples)
{
m_smo_size = sizeof(AudioBuffer) + total_samples * sizeof(AudioBuffer::sample_t);
m_smo_key = smo_create(m_smo_size, PROT_READ | PROT_WRITE);
if (m_smo_key == -1)
return BAN::Error::from_errno(errno);
m_audio_buffer = static_cast<AudioBuffer*>(smo_map(m_smo_key));
if (m_audio_buffer == nullptr)
return BAN::Error::from_errno(errno);
new (m_audio_buffer) AudioBuffer();
memset(m_audio_buffer->samples, 0, total_samples * sizeof(AudioBuffer::sample_t));
m_audio_buffer->capacity = total_samples;
if (m_audio_loader)
{
m_audio_buffer->channels = m_audio_loader->channels();
m_audio_buffer->sample_rate = m_audio_loader->sample_rate();
}
update();
sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, s_audio_server_socket.data());
m_server_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (m_server_fd == -1)
return BAN::Error::from_errno(errno);
if (connect(m_server_fd, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)) == -1)
return BAN::Error::from_errno(errno);
return {};
}
void Audio::update()
{
if (!m_audio_loader)
return;
while (m_audio_loader->samples_remaining())
{
const uint32_t next_head = (m_audio_buffer->head + 1) % m_audio_buffer->capacity;
if (next_head == m_audio_buffer->tail)
break;
m_audio_buffer->samples[m_audio_buffer->head] = m_audio_loader->get_sample();
m_audio_buffer->head = next_head;
}
}
}

View File

@ -0,0 +1,66 @@
#include <LibAudio/AudioLoader.h>
#include <LibAudio/AudioLoaders/WAVLoader.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
namespace LibAudio
{
BAN::ErrorOr<BAN::UniqPtr<AudioLoader>> AudioLoader::load(BAN::StringView path)
{
if (path.empty())
return BAN::Error::from_errno(ENOENT);
const bool malloced = (path.data()[path.size()] != '\0');
const char* path_cstr = path.data();
if (malloced && (path_cstr = strndup(path.data(), path.size())) == nullptr)
return BAN::Error::from_errno(errno);
const int file_fd = open(path_cstr, O_RDONLY);
if (malloced)
free(const_cast<char*>(path_cstr));
if (file_fd == -1)
return BAN::Error::from_errno(errno);
struct stat st;
if (fstat(file_fd, &st) == -1)
{
close(file_fd);
return BAN::Error::from_errno(errno);
}
uint8_t* mmap_addr = static_cast<uint8_t*>(mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, file_fd, 0));
close(file_fd);
if (mmap_addr == MAP_FAILED)
return BAN::Error::from_errno(errno);
BAN::ErrorOr<BAN::UniqPtr<AudioLoader>> result_or_error { BAN::Error::from_errno(ENOTSUP) };
auto file_span = BAN::ConstByteSpan(mmap_addr, st.st_size);
if (WAVAudioLoader::can_load_from(file_span))
result_or_error = WAVAudioLoader::create(file_span);
if (result_or_error.is_error())
{
munmap(mmap_addr, st.st_size);
return result_or_error.release_error();
}
auto result = result_or_error.release_value();
result->m_mmap_addr = mmap_addr;
result->m_mmap_size = st.st_size;
return result;
}
AudioLoader::~AudioLoader()
{
if (m_mmap_addr)
munmap(m_mmap_addr, m_mmap_size);
m_mmap_addr = nullptr;
m_mmap_size = 0;
}
}

View File

@ -0,0 +1,146 @@
#include <LibAudio/AudioLoaders/WAVLoader.h>
namespace LibAudio
{
struct WAVChunk
{
char chunk_id[4];
uint32_t chunk_size;
};
struct RIFFChunk : WAVChunk
{
char wave_id[4];
};
struct FormatChunk : WAVChunk
{
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplePerSec;
uint32_t nAvgBytePerSec;
uint16_t nBlockAlign;
uint16_t wBitsPerSample;
};
bool WAVAudioLoader::can_load_from(BAN::ConstByteSpan data)
{
if (data.size() < sizeof(RIFFChunk))
return false;
const auto riff_chunk = data.as<const RIFFChunk>();
if (memcmp(riff_chunk.chunk_id, "RIFF", 4) != 0)
return false;
if (memcmp(riff_chunk.wave_id, "WAVE", 4) != 0)
return false;
return true;
}
BAN::ErrorOr<BAN::UniqPtr<AudioLoader>> WAVAudioLoader::create(BAN::ConstByteSpan data)
{
ASSERT(can_load_from(data));
{
const auto riff_chunk = data.as<const RIFFChunk>();
if (sizeof(WAVChunk) + riff_chunk.chunk_size > data.size())
return BAN::Error::from_errno(ENOBUFS);
data = data.slice(0, sizeof(WAVChunk) + riff_chunk.chunk_size);
data = data.slice(sizeof(RIFFChunk));
}
BAN::Optional<FormatChunk> format_chunk;
BAN::ConstByteSpan sample_data;
while (!data.empty())
{
const auto chunk = data.as<const WAVChunk>();
if (data.size() < sizeof(WAVChunk) + chunk.chunk_size)
return BAN::Error::from_errno(ENOBUFS);
if (memcmp(chunk.chunk_id, "fmt ", 4) == 0)
format_chunk = data.as<const FormatChunk>();
else if (memcmp(chunk.chunk_id, "data", 4) == 0)
sample_data = data.slice(sizeof(WAVChunk), chunk.chunk_size);
data = data.slice(sizeof(WAVChunk) + chunk.chunk_size);
}
if (!format_chunk.has_value() || sample_data.empty())
return BAN::Error::from_errno(EINVAL);
const auto format = static_cast<FormatCode>(format_chunk->wFormatTag);
const uint16_t bps = format_chunk->wBitsPerSample;
const uint16_t channels = format_chunk->nChannels;
if (channels == 0)
return BAN::Error::from_errno(EINVAL);
switch (format)
{
case FormatCode::WAVE_FORMAT_PCM:
if (bps != 8 && bps != 16 && bps != 32)
return BAN::Error::from_errno(ENOTSUP);
break;
case FormatCode::WAVE_FORMAT_IEEE_FLOAT:
if (bps != 32 && bps != 64)
return BAN::Error::from_errno(ENOTSUP);
break;
default:
return BAN::Error::from_errno(ENOTSUP);
}
if (bps / 8 * channels != format_chunk->nBlockAlign)
return BAN::Error::from_errno(EINVAL);
auto loader = TRY(BAN::UniqPtr<WAVAudioLoader>::create());
loader->m_bits_per_sample = bps;
loader->m_sample_format = format;
loader->m_channels = channels;
loader->m_sample_rate = format_chunk->nSamplePerSec;
loader->m_sample_data = sample_data;
loader->m_total_samples = sample_data.size() / (bps / 8);
loader->m_current_sample = 0;
return BAN::UniqPtr<AudioLoader>(BAN::move(loader));
}
template<typename T>
static double read_sample(BAN::ConstByteSpan data)
{
if constexpr(BAN::is_same_v<float, T> || BAN::is_same_v<double, T>)
return data.as<const T>();
else if constexpr(BAN::is_signed_v<T>)
return data.as<const T>() / static_cast<double>(BAN::numeric_limits<T>::max());
else
return data.as<const T>() / (BAN::numeric_limits<T>::max() / 2.0) - 1.0;
}
double WAVAudioLoader::get_sample()
{
ASSERT(samples_remaining() > 0);
const auto current_sample_data = m_sample_data.slice((m_bits_per_sample / 8) * m_current_sample++);
switch (m_sample_format)
{
case FormatCode::WAVE_FORMAT_PCM:
switch (m_bits_per_sample)
{
case 8: return read_sample<uint8_t>(current_sample_data);
case 16: return read_sample<int16_t>(current_sample_data);
case 32: return read_sample<int32_t>(current_sample_data);
}
break;
case FormatCode::WAVE_FORMAT_IEEE_FLOAT:
switch (m_bits_per_sample)
{
case 32: return read_sample<float>(current_sample_data);
case 64: return read_sample<double>(current_sample_data);
}
break;
}
ASSERT_NOT_REACHED();
}
}

View File

@ -0,0 +1,12 @@
set(LIBAUDIO_SOURCES
Audio.cpp
AudioLoader.cpp
AudioLoaders/WAVLoader.cpp
)
add_library(libaudio ${LIBAUDIO_SOURCES})
banan_link_library(libaudio ban)
banan_link_library(libaudio libc)
banan_install_headers(libaudio)
install(TARGETS libaudio OPTIONAL)

View File

@ -0,0 +1,64 @@
#pragma once
#include <BAN/Atomic.h>
#include <BAN/StringView.h>
#include <BAN/Vector.h>
#include <LibAudio/AudioLoader.h>
namespace LibAudio
{
static constexpr BAN::StringView s_audio_server_socket = "/tmp/audio-server.socket"_sv;
struct AudioBuffer
{
using sample_t = double;
uint32_t sample_rate;
uint32_t channels;
uint32_t capacity;
BAN::Atomic<uint32_t> tail { 0 };
BAN::Atomic<uint32_t> head { 0 };
sample_t samples[/* capacity */];
};
class Audio
{
BAN_NON_COPYABLE(Audio);
public:
static BAN::ErrorOr<Audio> load(BAN::StringView path);
static BAN::ErrorOr<Audio> random(uint32_t samples);
~Audio() { clear(); }
Audio(Audio&& other) { *this = BAN::move(other); }
Audio& operator=(Audio&& other);
BAN::ErrorOr<void> start();
bool is_playing() const { return m_audio_buffer->tail != m_audio_buffer->head; }
void update();
private:
Audio() = default;
Audio(BAN::UniqPtr<AudioLoader>&& audio_loader)
: m_audio_loader(BAN::move(audio_loader))
{ }
void clear();
BAN::ErrorOr<void> initialize(uint32_t total_samples);
private:
int m_server_fd { -1 };
BAN::UniqPtr<AudioLoader> m_audio_loader;
long m_smo_key { -1 };
size_t m_smo_size { 0 };
AudioBuffer* m_audio_buffer { nullptr };
};
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <BAN/StringView.h>
#include <BAN/UniqPtr.h>
namespace LibAudio
{
class AudioLoader
{
BAN_NON_COPYABLE(AudioLoader);
BAN_NON_MOVABLE(AudioLoader);
public:
static BAN::ErrorOr<BAN::UniqPtr<AudioLoader>> load(BAN::StringView path);
virtual ~AudioLoader();
virtual uint32_t channels() const = 0;
virtual uint32_t sample_rate() const = 0;
virtual uint32_t samples_remaining() const = 0;
virtual double get_sample() = 0;
protected:
AudioLoader() = default;
private:
void* m_mmap_addr { nullptr };
size_t m_mmap_size { 0 };
};
}

View File

@ -0,0 +1,40 @@
#pragma once
#include <LibAudio/AudioLoader.h>
#include <BAN/ByteSpan.h>
namespace LibAudio
{
class WAVAudioLoader : public AudioLoader
{
public:
enum FormatCode : uint16_t
{
WAVE_FORMAT_PCM = 0x01,
WAVE_FORMAT_IEEE_FLOAT = 0x03,
};
public:
static bool can_load_from(BAN::ConstByteSpan data);
static BAN::ErrorOr<BAN::UniqPtr<AudioLoader>> create(BAN::ConstByteSpan data);
uint32_t channels() const override { return m_channels; }
uint32_t sample_rate() const override { return m_sample_rate; }
uint32_t samples_remaining() const override { return m_total_samples - m_current_sample; }
double get_sample() override;
private:
uint32_t m_channels { 0 };
uint32_t m_sample_rate { 0 };
uint32_t m_total_samples { 0 };
FormatCode m_sample_format;
uint16_t m_bits_per_sample { 0 };
size_t m_current_sample { 0 };
BAN::ConstByteSpan m_sample_data;
};
}

View File

@ -47,6 +47,11 @@ struct winsize
#define TIOCGWINSZ 50
#define TIOCSWINSZ 51
#define SND_GET_CHANNELS 60 /* stores number of channels to uint32_t argument */
#define SND_GET_SAMPLE_RATE 61 /* stores sample rate to uint32_t argument */
#define SND_RESET_BUFFER 62 /* stores the size of internal buffer to uint32_t argument and clears the buffer */
#define SND_GET_BUFFERSZ 63 /* stores the size of internal buffer to uint32_t argument */
int ioctl(int, int, ...);
__END_DECLS

View File

@ -231,13 +231,13 @@ static int exec_impl(const char* pathname, char* const* argv, char* const* envp,
while (*cur)
{
const char* end = strchrnul(cur, ':');
size_t len = end - cur;
const size_t len = end - cur;
ASSERT(strlen(pathname) + 1 + len < sizeof(buffer));
strncpy(buffer, cur, len);
strcat(buffer, "/");
strcat(buffer, pathname);
memcpy(buffer, cur, len);
buffer[len] = '/';
strcpy(buffer + len + 1, pathname);
struct stat st;
if (stat(buffer, &st) == 0)

View File

@ -0,0 +1,200 @@
#include "AudioServer.h"
#include <sys/banan-os.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
AudioServer::AudioServer(int fd)
: m_audio_device_fd(fd)
{
if (ioctl(m_audio_device_fd, SND_GET_CHANNELS, &m_channels) != 0)
ASSERT_NOT_REACHED();
if (ioctl(m_audio_device_fd, SND_GET_SAMPLE_RATE, &m_sample_rate) != 0)
ASSERT_NOT_REACHED();
}
BAN::ErrorOr<void> AudioServer::on_new_client(int fd)
{
TRY(m_audio_buffers.emplace(fd, nullptr));
return {};
}
void AudioServer::on_client_disconnect(int fd)
{
auto it = m_audio_buffers.find(fd);
ASSERT(it != m_audio_buffers.end());
if (it->value.buffer != nullptr)
{
const size_t bytes = sizeof(LibAudio::AudioBuffer) + it->value.buffer->capacity * sizeof(LibAudio::AudioBuffer::sample_t);
munmap(it->value.buffer, bytes);
}
m_audio_buffers.remove(it);
reset_kernel_buffer();
}
bool AudioServer::on_client_packet(int fd, long smo_key)
{
auto& audio_buffer = m_audio_buffers[fd];
audio_buffer.buffer = static_cast<LibAudio::AudioBuffer*>(smo_map(smo_key));
audio_buffer.sample_frames_queued = 0;
if (audio_buffer.buffer == nullptr)
{
dwarnln("Failed to map audio buffer: {}", strerror(errno));
return false;
}
reset_kernel_buffer();
return true;
}
void AudioServer::update()
{
uint32_t kernel_buffer_size;
if (ioctl(m_audio_device_fd, SND_GET_BUFFERSZ, &kernel_buffer_size) == -1)
ASSERT_NOT_REACHED();
const size_t kernel_samples = kernel_buffer_size / sizeof(int16_t);
ASSERT(kernel_samples <= m_samples_sent);
const uint32_t samples_played = m_samples_sent - kernel_samples;
ASSERT(samples_played % m_channels == 0);
for (uint32_t i = 0; i < samples_played; i++)
m_samples.pop();
m_samples_sent -= samples_played;
const size_t max_sample_frames = (m_samples.capacity() - m_samples.size()) / m_channels;
const size_t queued_samples_end = m_samples.size();
if (max_sample_frames == 0)
return;
for (auto& [_, buffer] : m_audio_buffers)
{
if (buffer.buffer == nullptr)
continue;
const double sample_ratio = buffer.buffer->sample_rate / static_cast<double>(m_sample_rate);
if (buffer.sample_frames_queued)
{
const uint32_t buffer_sample_frames_played = BAN::Math::min<size_t>(
samples_played * sample_ratio / m_channels,
buffer.sample_frames_queued
);
buffer.buffer->tail = (buffer.buffer->tail + buffer_sample_frames_played * buffer.buffer->channels) % buffer.buffer->capacity;
buffer.sample_frames_queued -= buffer_sample_frames_played;
}
const uint32_t buffer_total_sample_frames = ((buffer.buffer->capacity + buffer.buffer->head - buffer.buffer->tail) % buffer.buffer->capacity) / buffer.buffer->channels;
const uint32_t buffer_sample_frames_available = (buffer_total_sample_frames - buffer.sample_frames_queued) / sample_ratio;
if (buffer_sample_frames_available == 0)
continue;
const size_t sample_frames_to_queue = BAN::Math::min<size_t>(max_sample_frames, buffer_sample_frames_available);
if (sample_frames_to_queue == 0)
continue;
while (m_samples.size() < queued_samples_end + sample_frames_to_queue * m_channels)
m_samples.push(0.0);
const size_t min_channels = BAN::Math::min(m_channels, buffer.buffer->channels);
const size_t buffer_tail = buffer.buffer->tail + buffer.sample_frames_queued * buffer.buffer->channels;
for (size_t i = 0; i < sample_frames_to_queue; i++)
{
const size_t buffer_frame = i * sample_ratio;
for (size_t j = 0; j < min_channels; j++)
m_samples[queued_samples_end + i * m_channels + j] += buffer.buffer->samples[(buffer_tail + buffer_frame * buffer.buffer->channels + j) % buffer.buffer->capacity];
}
buffer.sample_frames_queued += sample_frames_to_queue * sample_ratio;
}
send_samples();
}
void AudioServer::reset_kernel_buffer()
{
uint32_t kernel_buffer_size;
if (ioctl(m_audio_device_fd, SND_RESET_BUFFER, &kernel_buffer_size) != 0)
ASSERT_NOT_REACHED();
const size_t kernel_samples = kernel_buffer_size / sizeof(int16_t);
ASSERT(kernel_samples <= m_samples_sent);
const uint32_t samples_played = m_samples_sent - kernel_samples;
ASSERT(samples_played % m_channels == 0);
m_samples_sent = 0;
m_samples.clear();
for (auto& [_, buffer] : m_audio_buffers)
{
if (buffer.buffer == nullptr || buffer.sample_frames_queued == 0)
continue;
const uint32_t buffer_sample_frames_played = BAN::Math::min<size_t>(
samples_played / m_channels,
buffer.sample_frames_queued
);
buffer.buffer->tail = (buffer.buffer->tail + buffer_sample_frames_played * buffer.buffer->channels) % buffer.buffer->capacity;
buffer.sample_frames_queued = 0;
}
update();
}
void AudioServer::send_samples()
{
// FIXME: don't assume kernel uses 16 bit PCM
using kernel_sample_t = int16_t;
if (m_samples_sent >= m_samples.size())
return;
while (m_samples_sent < m_samples.size())
{
const size_t samples_to_send = BAN::Math::min(m_send_buffer.size() / sizeof(kernel_sample_t), m_samples.size() - m_samples_sent);
auto buffer = BAN::ByteSpan(m_send_buffer.data(), samples_to_send * sizeof(kernel_sample_t));
{
auto sample_buffer = buffer.as_span<kernel_sample_t>();
for (size_t i = 0; i < samples_to_send; i++)
{
sample_buffer[i] = BAN::Math::clamp<double>(
m_samples[m_samples_sent + i] * BAN::numeric_limits<kernel_sample_t>::max(),
BAN::numeric_limits<kernel_sample_t>::min(),
BAN::numeric_limits<kernel_sample_t>::max()
);
}
}
size_t nwritten = 0;
while (nwritten < buffer.size())
{
const ssize_t nwrite = write(
m_audio_device_fd,
buffer.data() + nwritten,
buffer.size() - nwritten
);
if (nwrite == -1)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
dwarnln("write: {}", strerror(errno));
break;
}
nwritten += nwrite;
}
m_samples_sent += nwritten / sizeof(kernel_sample_t);
if (nwritten < buffer.size())
break;
}
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <BAN/Array.h>
#include <BAN/ByteSpan.h>
#include <BAN/CircularQueue.h>
#include <BAN/HashMap.h>
#include <LibAudio/Audio.h>
class AudioServer
{
BAN_NON_MOVABLE(AudioServer);
BAN_NON_COPYABLE(AudioServer);
public:
AudioServer(int audio_device_fd);
BAN::ErrorOr<void> on_new_client(int fd);
void on_client_disconnect(int fd);
bool on_client_packet(int fd, long smo_key);
void update();
private:
struct ClientInfo
{
LibAudio::AudioBuffer* buffer;
size_t sample_frames_queued { 0 };
};
private:
enum class AddOrRemove { Add, Remove };
void reset_kernel_buffer();
void send_samples();
private:
const int m_audio_device_fd;
uint32_t m_sample_rate;
uint32_t m_channels;
size_t m_samples_sent { 0 };
BAN::Array<uint8_t, 1024> m_send_buffer;
BAN::CircularQueue<double, 64 * 1024> m_samples;
BAN::HashMap<int, ClientInfo> m_audio_buffers;
};

View File

@ -0,0 +1,11 @@
set(SOURCES
main.cpp
AudioServer.cpp
)
add_executable(AudioServer ${SOURCES})
banan_link_library(AudioServer ban)
banan_link_library(AudioServer libc)
banan_link_library(AudioServer libaudio)
install(TARGETS AudioServer OPTIONAL)

View File

@ -0,0 +1,184 @@
#include "AudioServer.h"
#include <LibAudio/Audio.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
static int open_server_fd()
{
struct stat st;
if (stat(LibAudio::s_audio_server_socket.data(), &st) != -1)
unlink(LibAudio::s_audio_server_socket.data());
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1)
{
dwarnln("failed to create server socket: {}", strerror(errno));
return -1;
}
sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, LibAudio::s_audio_server_socket.data());
if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
dwarnln("failed to bind server socket: {}", strerror(errno));
return -1;
}
if (chmod(LibAudio::s_audio_server_socket.data(), 0777) == -1)
{
dwarnln("failed to set server socket permissions: {}", strerror(errno));
return -1;
}
if (listen(server_fd, SOMAXCONN) == -1)
{
dwarnln("failed to make server socket listening: {}", strerror(errno));
return -1;
}
return server_fd;
}
static uint64_t get_current_ms()
{
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
int main()
{
constexpr int non_terminating_signals[] {
SIGCONT,
SIGSTOP,
SIGTSTP,
SIGTTIN,
SIGTTOU,
};
for (int sig = _SIGMIN; sig <= _SIGMAX; sig++)
signal(sig, exit);
for (int sig : non_terminating_signals)
signal(sig, SIG_DFL);
const int audio_device_fd = open("/dev/audio0", O_RDWR | O_NONBLOCK);
if (audio_device_fd == -1)
{
dwarnln("failed to open audio device: {}", strerror(errno));
return 1;
}
auto* audio_server = new AudioServer(audio_device_fd);
if (audio_server == nullptr)
{
dwarnln("Failed to allocate AudioServer: {}", strerror(errno));
return 1;
}
const int server_fd = open_server_fd();
if (server_fd == -1)
return 1;
struct ClientInfo
{
int fd;
};
BAN::Vector<ClientInfo> clients;
dprintln("AudioServer started");
constexpr uint64_t update_interval_ms = 100;
uint64_t next_update_ms = get_current_ms() + update_interval_ms;
for (;;)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(server_fd, &fds);
int max_fd = server_fd;
for (const auto& client : clients)
{
max_fd = BAN::Math::max(max_fd, client.fd);
FD_SET(client.fd, &fds);
}
const uint64_t current_ms = get_current_ms();
if (current_ms >= next_update_ms)
{
audio_server->update();
next_update_ms = current_ms + update_interval_ms;
}
const uint64_t timeout_ms = next_update_ms - current_ms;
timeval timeout {
.tv_sec = static_cast<time_t>(timeout_ms / 1000),
.tv_usec = static_cast<suseconds_t>((timeout_ms % 1000) * 1000)
};
if (select(max_fd + 1, &fds, nullptr, nullptr, &timeout) == -1)
{
dwarnln("select: {}", strerror(errno));
break;
}
if (FD_ISSET(server_fd, &fds))
{
const int client_fd = accept(server_fd, nullptr, nullptr);
if (client_fd == -1)
{
dwarnln("accept: {}", strerror(errno));
continue;
}
if (auto ret = clients.emplace_back(client_fd); ret.is_error())
{
dwarnln("Failed to add client: {}", ret.error());
close(client_fd);
continue;
}
if (auto ret = audio_server->on_new_client(client_fd); ret.is_error())
{
dwarnln("Failed to initialize client: {}", ret.error());
clients.pop_back();
close(client_fd);
continue;
}
}
for (size_t i = 0; i < clients.size(); i++)
{
auto& client = clients[i];
if (!FD_ISSET(client.fd, &fds))
continue;
long smo_key;
const ssize_t nrecv = recv(client.fd, &smo_key, sizeof(smo_key), 0);
if (nrecv < static_cast<ssize_t>(sizeof(smo_key)) || !audio_server->on_client_packet(client.fd, smo_key))
{
if (nrecv == 0)
;
else if (nrecv < 0)
dwarnln("recv: {}", strerror(errno));
else if (nrecv < static_cast<ssize_t>(sizeof(smo_key)))
dwarnln("client sent only {} bytes, {} expected", nrecv, sizeof(smo_key));
audio_server->on_client_disconnect(client.fd);
close(client.fd);
clients.remove(i--);
continue;
}
}
}
}

View File

@ -1,4 +1,6 @@
set(USERSPACE_PROGRAMS
audio
AudioServer
bananfetch
basename
cat

View File

@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(audio ${SOURCES})
banan_link_library(audio libc)
banan_link_library(audio libaudio)
install(TARGETS audio OPTIONAL)

View File

@ -0,0 +1,36 @@
#include <LibAudio/Audio.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (argc != 2)
{
fprintf(stderr, "usage: %s FILE\n", argv[0]);
return 1;
}
auto audio_or_error = LibAudio::Audio::load(argv[1]);
if (audio_or_error.is_error())
{
fprintf(stderr, "failed to load %s: %s\n", argv[1], audio_or_error.error().get_message());
return 1;
}
auto audio = audio_or_error.release_value();
if (auto ret = audio.start(); ret.is_error())
{
fprintf(stderr, "failed start playing audio: %s\n", ret.error().get_message());
return 1;
}
while (audio.is_playing())
{
usleep(10'000);
audio.update();
}
return 0;
}