From 404c31ed857ca2660c3688c933838280615ef144 Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Tue, 2 Sep 2025 22:07:56 +0200 Subject: [PATCH 1/4] Add snd module with sb16 driver --- Makefile | 1 + src/lib.rs | 1 + src/sys/mem/phys.rs | 4 ++ src/sys/mod.rs | 1 + src/sys/snd/mod.rs | 6 ++ src/sys/snd/sb16.rs | 158 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 171 insertions(+) create mode 100644 src/sys/snd/mod.rs create mode 100644 src/sys/snd/sb16.rs diff --git a/Makefile b/Makefile index 78a70ca6b..2bc3c7db6 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,7 @@ image: $(img) qemu-opts = -name "MOROS $$MOROS_VERSION" \ -m $(memory) -smp $(smp) -drive file=$(img),format=raw \ -audiodev $(audio),id=a0 -machine pcspk-audiodev=a0 \ + -audio driver=$(audio),model=sb16 \ -netdev user,id=e0,hostfwd=tcp::8080-:80 -device $(nic),netdev=e0 ifeq ($(kvm),true) qemu-opts += -cpu host -accel kvm diff --git a/src/lib.rs b/src/lib.rs index 91e8189dd..aa40b1cf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ pub fn init(boot_info: &'static BootInfo) { sys::acpi::init(); // Require MEM sys::rng::init(); sys::pci::init(); // Require MEM + sys::snd::init(); sys::net::init(); // Require PCI sys::ata::init(); sys::fs::init(); // Require ATA diff --git a/src/sys/mem/phys.rs b/src/sys/mem/phys.rs index c3f8b596c..ee49124e5 100644 --- a/src/sys/mem/phys.rs +++ b/src/sys/mem/phys.rs @@ -32,6 +32,10 @@ impl PhysBuf { pub fn addr(&self) -> u64 { phys_addr(&self.buf.lock()[0]) } + + pub fn size(&self) -> usize { + self.buf.lock().len() + } } impl> Index for PhysBuf { diff --git a/src/sys/mod.rs b/src/sys/mod.rs index a00aa5b5c..6b5722701 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -54,6 +54,7 @@ pub mod pic; pub mod process; pub mod rng; pub mod serial; +pub mod snd; pub mod speaker; pub mod syscall; pub mod vga; diff --git a/src/sys/snd/mod.rs b/src/sys/snd/mod.rs new file mode 100644 index 000000000..bffcbf6d4 --- /dev/null +++ b/src/sys/snd/mod.rs @@ -0,0 +1,6 @@ +mod sb16; + + +pub fn init() { + sb16::init(); +} diff --git a/src/sys/snd/sb16.rs b/src/sys/snd/sb16.rs new file mode 100644 index 000000000..ec0f4a818 --- /dev/null +++ b/src/sys/snd/sb16.rs @@ -0,0 +1,158 @@ +use crate::sys; +use crate::sys::mem::PhysBuf; + +use alloc::vec::Vec; +use spin::Mutex; +use x86_64::instructions::port::Port; + +// Sources: +// https://wiki.osdev.org/Sound_Blaster_16 +// https://pdos.csail.mit.edu/6.828/2006/readings/hardware/SoundBlaster.pdf + +const MIXER_ADDR: u16 = 0x224; +const MIXER_DATA: u16 = 0x225; +const DSP_RESET: u16 = 0x226; +const DSP_READ: u16 = 0x22A; +const DSP_WRITE: u16 = 0x22C; +const DSP_ACK: u16 = 0x22E; + +const IRQ: u8 = 5; + +pub const BUF_LEN: usize = 32 << 10; + +pub static SND: Mutex>)>> = Mutex::new(None); + +fn irq(num: u8) -> u8 { + match num { + 2 => 0x01, + 5 => 0x02, + 7 => 0x04, + 10 => 0x08, + _ => panic!(), + } +} + +fn outb(addr: u16, value: u8) { + let mut port: Port = Port::new(addr); + unsafe { + port.write(value); + } +} + +fn inb(addr: u16) -> u8 { + let mut port: Port = Port::new(addr); + unsafe { + port.read() + } +} + +fn reset() { + outb(DSP_RESET, 1); + sys::clk::wait(3000); + outb(DSP_RESET, 0); + loop { + let res = inb(DSP_READ); + if res == 0xAA { + break; + } + } +} + +fn version() -> u8 { + outb(DSP_WRITE, 0xE1); + inb(DSP_WRITE) +} + +fn dma(addr: u64, size: usize) { + let addr = addr.to_le_bytes(); + let size = size.to_le_bytes(); + let chan = 1; + outb(0x0A, 0x04 + chan); // Disable channel + outb(0x0C, 0x01); // Flip flop + outb(0x0B, 0x58 + chan); // Send transfer mode + outb(0x83, addr[2]); // Send page number + outb(0x02, addr[0]); // Send low bits of addr + outb(0x02, addr[1]); // Send high bits of addr + outb(0x03, size[0]); // Send low bits of size + outb(0x03, size[1]); // Send high bits of size + outb(0x0A, chan); // Enable channel +} + +pub fn stop() { + if let Some((ref mut buf, ref mut queue)) = *SND.lock() { + outb(MIXER_ADDR, 0xD0); + let chan = 1; + outb(0x0A, 0x04 + chan); // Disable channel + buf.fill(0x80); + queue.clear(); + } +} + +pub fn play(pcm: &[u8]) { + if let Some((ref mut buf, ref mut queue)) = *SND.lock() { + queue.clear(); + for chunk in pcm.chunks(buf.len()) { + queue.push(chunk.to_vec()); + } + let pcm = queue.remove(0); + let len = core::cmp::min(buf.len(), pcm.len()); + buf[0..len].copy_from_slice(&pcm[0..len]); + buf[len..].fill(0x80); + + // Set sample rate + let rate: u16 = 22050; + let rate = rate.to_le_bytes(); + outb(DSP_WRITE, 0x41); // Sample rate + outb(DSP_WRITE, rate[1]); + outb(DSP_WRITE, rate[0]); + + outb(DSP_WRITE, 0xC6); // 8 bit sound played continuously + outb(DSP_WRITE, 0x00); // Mono and unsigned sound data + + // Set DMA + dma(buf.addr(), buf.size() - 1); + let bytes = (buf.size() - 1).to_le_bytes(); + outb(DSP_WRITE, bytes[0]); + outb(DSP_WRITE, bytes[1]); + } +} + +pub fn init() { + if version() != 0xFF { + reset(); + + // Set IRQ + sys::idt::set_irq_handler(IRQ, interrupt_handler); + outb(MIXER_ADDR, 0x80); + outb(MIXER_DATA, irq(IRQ)); + + let buf = PhysBuf::new(BUF_LEN); + let queue = Vec::new(); + *SND.lock() = Some((buf, queue)); + log!("SND DRV SB16"); + + /* + // Play a square wave + let mut pcm = [0; BUF_LEN]; + for i in 0..pcm.len() { + pcm[i] = if (i / 64) % 2 == 0 { 0x00 } else { 0xFF }; + } + play(&pcm); + */ + } +} + +fn interrupt_handler() { + if let Some((ref mut buf, ref mut queue)) = *SND.lock() { + if queue.is_empty() { + let chan = 1; + outb(0x0A, 0x04 + chan); // Disable channel + } else { + let pcm = queue.remove(0); + let len = core::cmp::min(buf.len(), pcm.len()); + buf[0..len].copy_from_slice(&pcm[0..len]); + buf[len..].fill(0x80); + } + } + let _ = inb(DSP_ACK); +} From 8ba7436a4afd213bdddf2d031fd71b170fab3c71 Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Tue, 2 Sep 2025 22:10:23 +0200 Subject: [PATCH 2/4] Add /dev/snd/buf device --- src/api/fs.rs | 1 + src/sys/fs/device.rs | 10 ++++++++++ src/usr/install.rs | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/api/fs.rs b/src/api/fs.rs index e5b16753c..a5920f955 100644 --- a/src/api/fs.rs +++ b/src/api/fs.rs @@ -170,6 +170,7 @@ fn device_type(name: &str) -> Result { "net-ip" => Ok(DeviceType::NetIp), "net-mac" => Ok(DeviceType::NetMac), "net-usage" => Ok(DeviceType::NetUsage), + "snd-buffer" => Ok(DeviceType::SndBuffer), "vga-buffer" => Ok(DeviceType::VgaBuffer), "vga-font" => Ok(DeviceType::VgaFont), "vga-mode" => Ok(DeviceType::VgaMode), diff --git a/src/sys/fs/device.rs b/src/sys/fs/device.rs index 9de46a814..22712befe 100644 --- a/src/sys/fs/device.rs +++ b/src/sys/fs/device.rs @@ -15,6 +15,7 @@ use crate::sys::net::socket::udp::UdpSocket; use crate::sys::rng::Random; use crate::sys::speaker::Speaker; use crate::sys::vga::{VgaFont, VgaMode, VgaPalette, VgaBuffer}; +use crate::sys::snd::SndBuffer; use alloc::vec; use alloc::vec::Vec; @@ -43,6 +44,7 @@ pub enum DeviceType { NetIp = 16, NetMac = 17, NetUsage = 18, + SndBuffer = 19, } impl TryFrom<&[u8]> for DeviceType { @@ -69,6 +71,7 @@ impl TryFrom<&[u8]> for DeviceType { 16 => Ok(DeviceType::NetIp), 17 => Ok(DeviceType::NetMac), 18 => Ok(DeviceType::NetUsage), + 19 => Ok(DeviceType::SndBuffer), _ => Err(()), } } @@ -94,6 +97,7 @@ impl DeviceType { DeviceType::NetIp => NetIp::size(), DeviceType::NetMac => NetMac::size(), DeviceType::NetUsage => NetUsage::size(), + DeviceType::SndBuffer => SndBuffer::size(), _ => 1, }; let mut res = vec![0; len]; @@ -123,6 +127,7 @@ pub enum Device { NetIp(NetIp), NetMac(NetMac), NetUsage(NetUsage), + SndBuffer(SndBuffer), } impl TryFrom<&[u8]> for Device { @@ -148,6 +153,7 @@ impl TryFrom<&[u8]> for Device { DeviceType::NetIp => Ok(Device::NetIp(NetIp::new())), DeviceType::NetMac => Ok(Device::NetMac(NetMac::new())), DeviceType::NetUsage => Ok(Device::NetUsage(NetUsage::new())), + DeviceType::SndBuffer => Ok(Device::SndBuffer(SndBuffer::new())), DeviceType::Drive if buf.len() > 2 => { let bus = buf[1]; let dsk = buf[2]; @@ -216,6 +222,7 @@ impl FileIO for Device { Device::NetIp(io) => io.read(buf), Device::NetMac(io) => io.read(buf), Device::NetUsage(io) => io.read(buf), + Device::SndBuffer(io) => io.read(buf), } } @@ -240,6 +247,7 @@ impl FileIO for Device { Device::NetIp(io) => io.write(buf), Device::NetMac(io) => io.write(buf), Device::NetUsage(io) => io.write(buf), + Device::SndBuffer(io) => io.write(buf), } } @@ -264,6 +272,7 @@ impl FileIO for Device { Device::NetIp(io) => io.close(), Device::NetMac(io) => io.close(), Device::NetUsage(io) => io.close(), + Device::SndBuffer(io) => io.close(), } } @@ -288,6 +297,7 @@ impl FileIO for Device { Device::NetIp(io) => io.poll(event), Device::NetMac(io) => io.poll(event), Device::NetUsage(io) => io.poll(event), + Device::SndBuffer(io) => io.poll(event), } } } diff --git a/src/usr/install.rs b/src/usr/install.rs index 5225be1bd..0a0642311 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -49,6 +49,7 @@ pub fn copy_files(verbose: bool) { create_dir("/dev/ata/1", verbose); create_dir("/dev/clk", verbose); // Clock create_dir("/dev/net", verbose); // Network + create_dir("/dev/snd", verbose); // Sound create_dir("/dev/vga", verbose); create_dev("/dev/ata/0/0", "ata-0-0", verbose); @@ -67,6 +68,7 @@ pub fn copy_files(verbose: bool) { create_dev("/dev/net/usage", "net-usage", verbose); create_dev("/dev/null", "null", verbose); create_dev("/dev/random", "random", verbose); + create_dev("/dev/snd/buf", "snd-buffer", verbose); create_dev("/dev/speaker", "speaker", verbose); create_dev("/dev/vga/buffer", "vga-buffer", verbose); create_dev("/dev/vga/font", "vga-font", verbose); From 73d34435121de55183637396cefbf5782240ed7f Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Tue, 2 Sep 2025 22:10:57 +0200 Subject: [PATCH 3/4] Allow reading /dev/null to stop playing sound --- src/sys/fs/device.rs | 2 +- src/usr/read.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sys/fs/device.rs b/src/sys/fs/device.rs index 22712befe..d0ecaadcb 100644 --- a/src/sys/fs/device.rs +++ b/src/sys/fs/device.rs @@ -203,7 +203,7 @@ impl Device { impl FileIO for Device { fn read(&mut self, buf: &mut [u8]) -> Result { match self { - Device::Null => Err(()), + Device::Null => Ok(0), Device::File(io) => io.read(buf), Device::Console(io) => io.read(buf), Device::Random(io) => io.read(buf), diff --git a/src/usr/read.rs b/src/usr/read.rs index d5503256d..b4dd76aea 100644 --- a/src/usr/read.rs +++ b/src/usr/read.rs @@ -100,6 +100,10 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> { return Ok(()); } if let Ok(bytes) = fs::read_to_bytes(path) { + if bytes.is_empty() { + print!(""); + return Ok(()); + } if is_char_device && bytes.len() == 1 { match bytes[0] as char { api::console::ETX_KEY => { From 94d9a3c8f94e2627c907dac1b48294275d6e8277 Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Tue, 2 Sep 2025 22:24:13 +0200 Subject: [PATCH 4/4] Add missing device implementation --- src/sys/snd/mod.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/sys/snd/mod.rs b/src/sys/snd/mod.rs index bffcbf6d4..87c644b1b 100644 --- a/src/sys/snd/mod.rs +++ b/src/sys/snd/mod.rs @@ -1,5 +1,43 @@ mod sb16; +use crate::api::fs::{FileIO, IO}; + +#[derive(Debug, Clone)] +pub struct SndBuffer; + +impl SndBuffer { + pub fn new() -> Self { + Self {} + } + + pub fn size() -> usize { + sb16::BUF_LEN + } +} + +impl FileIO for SndBuffer { + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(()) + } + + fn write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + sb16::stop(); + } else { + sb16::play(buf); + } + Ok(buf.len()) + } + + fn close(&mut self) {} + + fn poll(&mut self, event: IO) -> bool { + match event { + IO::Read => false, + IO::Write => true, + } + } +} pub fn init() { sb16::init();