sound blaster code doesn't work yet
authorJohn Tsiombikas <nuclear@mutantstargoat.com>
Thu, 24 May 2018 19:15:14 +0000 (22:15 +0300)
committerJohn Tsiombikas <nuclear@mutantstargoat.com>
Thu, 24 May 2018 19:15:14 +0000 (22:15 +0300)
12 files changed:
.gitattributes
Makefile
click-s8_mono.pcm [new file with mode: 0644]
src/au_sb.c
src/au_sb.h
src/audio.c
src/audio.h
src/dma.c [new file with mode: 0644]
src/dma.h [new file with mode: 0644]
src/kmain.c
src/test/ausamples.s [new file with mode: 0644]
src/test/vbetest.c

index 74778d1..de879ae 100644 (file)
@@ -1,2 +1,3 @@
 *.raw filter=lfs diff=lfs merge=lfs -text
 *.pal filter=lfs diff=lfs merge=lfs -text
+*.pcm filter=lfs diff=lfs merge=lfs -text
index 07e856f..9622719 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 csrc = $(wildcard src/*.c) $(wildcard src/libc/*.c) $(wildcard src/test/*.c)
-ssrc = $(wildcard src/*.s) $(wildcard src/libc/*.s) $(wildcard src/boot/*.s)
+ssrc = $(wildcard src/*.s) $(wildcard src/libc/*.s) $(wildcard src/boot/*.s) $(wildcard src/test/*.s)
 Ssrc = $(wildcard src/*.S)
 obj = $(csrc:.c=.o) $(ssrc:.s=.o) $(Ssrc:.S=.o)
 dep = $(obj:.o=.d)
diff --git a/click-s8_mono.pcm b/click-s8_mono.pcm
new file mode 100644 (file)
index 0000000..184f5dc
Binary files /dev/null and b/click-s8_mono.pcm differ
index f3184ae..b42a603 100644 (file)
@@ -19,6 +19,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include "audio.h"
 #include "au_sb.h"
 #include "asmops.h"
+#include "intr.h"
+#include "dma.h"
 
 #define REG_MIXPORT            (base_port + 0x4)
 #define REG_MIXDATA            (base_port + 0x5)
@@ -33,20 +35,49 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #define WSTAT_BUSY             0x80
 #define RSTAT_RDY              0x80
 
-#define CMD_SET_RATE   0x41
-#define CMD_PLAY_PCM   0xa6
-#define CMD_STOP_PCM   0xd9
-#define CMD_GET_VER            0xe1
+#define CMD_RATE                       0x40
+#define CMD_SB16_OUT_RATE      0x41
+#define CMD_SB16_IN_RATE       0x42
+#define CMD_GET_VER                    0xe1
+
+/* start DMA playback/recording. combine with fifo/auto/input flags */
+#define CMD_START_DMA8         0xc0
+#define CMD_START_DMA16                0xb0
+#define CMD_FIFO                       0x02
+#define CMD_AUTO                       0x04
+#define CMD_INPUT                      0x08
+
+/* immediately pause/continue */
+#define CMD_PAUSE_DMA8         0xd0
+#define CMD_CONT_DMA8          0xd4
+#define CMD_PAUSE_DMA16                0xd5
+#define CMD_CONT_DMA16         0xd6
+
+/* end the playback at the end of the current buffer */
+#define CMD_END_DMA16          0xd9
+#define CMD_END_DMA8           0xda
+
+/* transfer mode commands */
+#define CMD_MODE_SIGNED                0x10
+#define CMD_MODE_STEREO                0x20
 
 #define VER_MAJOR(x)   ((x) >> 8)
 #define VER_MINOR(x)   ((x) & 0xff)
 
+static void intr_handler();
+static void start_dma_transfer(uint32_t addr, int size);
 static void write_dsp(unsigned char val);
 static unsigned char read_dsp(void);
 static int get_dsp_version(void);
 static const char *sbname(int ver);
 
+extern unsigned char low_mem_buffer[];
+
 static int base_port;
+static int irq, dma_chan;
+static int sb16;
+static void *buffer;
+static int xfer_mode;
 
 int sb_detect(void)
 {
@@ -56,12 +87,17 @@ int sb_detect(void)
                base_port = 0x200 + ((i + 1) << 4);
                if(sb_reset_dsp() == 0) {
                        ver = get_dsp_version();
+                       sb16 = VER_MAJOR(ver) >= 4;
                        printf("sb_detect: found %s (DSP v%d.%02d) at port %xh\n", sbname(ver),
                                        VER_MAJOR(ver), VER_MINOR(ver), base_port);
                        return 1;
                }
        }
 
+       /* TODO: detect these somehow? */
+       irq = 5;
+       dma_chan = 1;
+
        return 0;
 }
 
@@ -84,6 +120,101 @@ int sb_reset_dsp(void)
        return -1;
 }
 
+void sb_set_output_rate(int rate)
+{
+       if(sb16) {
+               write_dsp(CMD_SB16_OUT_RATE);
+               write_dsp(rate >> 8);
+               write_dsp(rate & 0xff);
+       } else {
+               int tcon = 256 - 1000000 / rate;
+               write_dsp(CMD_RATE);
+               write_dsp(tcon);
+       }
+}
+
+void *sb_buffer(int *size)
+{
+       *size = 65536;
+       return buffer;
+}
+
+void sb_start(int rate, int nchan)
+{
+       uint32_t addr;
+       int size;
+
+       /* for now just use the are after boot2. it's only used by the second stage
+        * loader and the VBE init code, none of which should overlap with audio playback.
+        * It's not necessary to use low memory. We can use up to the first 16mb for this,
+        * so if this becomes an issue, I'll make a DMA buffer allocator over 1mb.
+        * start the buffer from the next 64k boundary.
+        */
+       addr = ((uint32_t)low_mem_buffer + 0xffff) & 0xffff0000;
+       buffer = (void*)addr;
+
+       xfer_mode = CMD_MODE_SIGNED;
+       if(nchan > 1) {
+               xfer_mode |= CMD_MODE_STEREO;
+       }
+
+       if(!(size = audio_callback(buffer, 65536))) {
+               return;
+       }
+
+       interrupt(IRQ_TO_INTR(irq), intr_handler);
+
+       start_dma_transfer(addr, size);
+}
+
+void sb_pause(void)
+{
+       write_dsp(CMD_PAUSE_DMA8);
+}
+
+void sb_continue(void)
+{
+       write_dsp(CMD_CONT_DMA8);
+}
+
+void sb_stop(void)
+{
+       write_dsp(CMD_END_DMA8);
+}
+
+void sb_volume(int vol)
+{
+       /* TODO */
+}
+
+static void intr_handler()
+{
+       int size;
+
+       /* ask for more data */
+       if(!(size = audio_callback(buffer, 65536))) {
+               sb_stop();
+               return;
+       }
+       start_dma_transfer((uint32_t)buffer, size);
+
+       /* acknowledge the interrupt */
+       inb(REG_INTACK);
+}
+
+static void start_dma_transfer(uint32_t addr, int size)
+{
+       /* set up the next DMA transfer */
+       dma_out(dma_chan, addr, size, DMA_SINGLE);
+
+       /* program the DSP to accept the DMA transfer */
+       write_dsp(CMD_START_DMA8 | CMD_FIFO);
+       write_dsp(xfer_mode);
+       size--;
+       write_dsp(size & 0xff);
+       write_dsp((size >> 8) & 0xff);
+}
+
 static void write_dsp(unsigned char val)
 {
        while(inb(REG_WSTAT) & WSTAT_BUSY);
index 9552bcd..d437ff7 100644 (file)
@@ -28,4 +28,15 @@ int sb_detect(void);
  */
 int sb_reset_dsp(void);
 
+void *sb_buffer(int *size);
+
+void sb_set_output_rate(int rate);
+
+void sb_start(int rate, int nchan);
+void sb_pause(void);
+void sb_continue(void);
+void sb_stop(void);
+
+void sb_volume(int vol);
+
 #endif /* AU_SB_H_ */
index c7142f1..49b8a8d 100644 (file)
@@ -19,12 +19,70 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include "audio.h"
 #include "au_sb.h"
 
-void init_audio(void)
+struct audrv {
+       void *(*get_buffer)(int *size);
+       void (*start)(int rate, int nchan);
+       void (*pause)(void);
+       void (*cont)(void);
+       void (*stop)(void);
+       void (*volume)(int vol);
+};
+
+static struct audrv drv;
+
+static audio_callback_func cbfunc;
+static void *cbcls;
+
+void audio_init(void)
 {
        if(sb_detect()) {
-               /* TODO use the sound blaster */
+               drv.get_buffer = sb_buffer;
+               drv.start = sb_start;
+               drv.pause = sb_pause;
+               drv.cont = sb_continue;
+               drv.stop = sb_stop;
+               drv.volume = sb_volume;
                return;
        }
 
        printf("No supported audio device detected\n");
 }
+
+void audio_set_callback(audio_callback_func func, void *cls)
+{
+       cbfunc = func;
+       cbcls = cls;
+}
+
+int audio_callback(void *buf, int sz)
+{
+       if(!cbfunc) {
+               return 0;
+       }
+       return cbfunc(buf, sz, cbcls);
+}
+
+void audio_play(int rate, int nchan)
+{
+       drv.start(rate, nchan);
+}
+
+void audio_pause(void)
+{
+       drv.pause();
+}
+
+void audio_resume(void)
+{
+       drv.cont();
+}
+
+void audio_stop(void)
+{
+       drv.stop();
+}
+
+void audio_volume(int vol)
+{
+       drv.volume(vol);
+}
index 60b6bd5..9b528c2 100644 (file)
@@ -18,6 +18,18 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #ifndef AUDIO_H_
 #define AUDIO_H_
 
-void init_audio(void);
+typedef int (*audio_callback_func)(void *buffer, int size, void *cls);
+
+void audio_init(void);
+
+void audio_set_callback(audio_callback_func func, void *cls);
+int audio_callback(void *buf, int sz);
+
+void audio_play(int rate, int nchan);
+void audio_pause(void);
+void audio_resume(void);
+void audio_stop(void);
+
+void audio_volume(int vol);
 
 #endif /* AUDIO_H_ */
diff --git a/src/dma.c b/src/dma.c
new file mode 100644 (file)
index 0000000..b6c4605
--- /dev/null
+++ b/src/dma.c
@@ -0,0 +1,150 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "dma.h"
+#include "asmops.h"
+
+/* 8bit DMA ports */
+#define DMA_0_ADDR     0x00
+#define DMA_0_COUNT    0x01
+#define DMA_1_ADDR     0x02
+#define DMA_1_COUNT    0x03
+#define DMA_2_ADDR     0x04
+#define DMA_2_COUNT    0x05
+#define DMA_3_ADDR     0x06
+#define DMA_3_COUNT    0x07
+/* 16bit DMA ports */
+#define DMA_4_ADDR     0xc0
+#define DMA_4_COUNT    0xc2
+#define DMA_5_ADDR     0xc4
+#define DMA_5_COUNT    0xc6
+#define DMA_6_ADDR     0xc8
+#define DMA_6_COUNT    0xca
+#define DMA_7_ADDR     0xcc
+#define DMA_7_COUNT    0xce
+
+#define DMA_ADDR(c)    \
+       ((c < 4) ? DMA_0_ADDR + ((c) << 1) : (DMA_4_ADDR + ((c) << 2)))
+#define DMA_COUNT(c) \
+       ((c < 4) ? DMA_0_COUNT + ((c) << 1) : (DMA_4_COUNT + ((c) << 2)))
+
+#define DMA8_MASK                      0x0a
+#define DMA8_MODE                      0x0b
+#define DMA8_CLR_FLIPFLOP      0x0c
+#define DMA8_RESET                     0x0d
+#define DMA8_MASK_RST          0x0e
+#define DMA8_RMASK                     0x0f
+#define DMA16_MASK                     0xd4
+#define DMA16_MODE                     0xd6
+#define DMA16_CLR_FLIPFLOP     0xd8
+#define DMA16_RESET                    0xda
+#define DMA16_MASK_RST         0xdc
+#define DMA16_RMASK            0xde
+
+#define DMA_MASK(c)    ((c) < 4 ? DMA8_MASK : DMA16_MASK)
+#define DMA_MODE(c)    ((c) < 4 ? DMA8_MODE : DMA16_MODE)
+#define DMA_CLR_FLIPFLOP(c)    ((c) < 4 ? DMA8_CLR_FLIPFLOP : DMA16_CLR_FLIPFLOP)
+#define DMA_RESET(c)   ((c) < 4 ? DMA8_RESET : DMA16_RESET)
+#define DMA_MASK_RST(c)        ((c) < 4 ? DMA8_MASK_RST : DMA16_MASK_RST)
+#define DMA_RMASK(c)   ((c) < 4 ? DMA8_RMASK : DMA16_RMASK)
+
+#define DMA_0_PAGE             0x87
+#define DMA_1_PAGE             0x83
+#define DMA_2_PAGE             0x81
+#define DMA_3_PAGE             0x82
+#define DMA_4_PAGE             0x8f
+#define DMA_5_PAGE             0x8b
+#define DMA_6_PAGE             0x89
+#define DMA_7_PAGE             0x8a
+
+#define MODE_CHAN(x)   ((x) & 3)
+#define MODE_WRITE             0x04
+#define MODE_READ              0x08
+#define MODE_AUTO              0x10
+#define MODE_DECR              0x20
+#define MODE_SINGLE            0x40
+#define MODE_BLOCK             0x80
+#define MODE_CASCADE   0xc0
+
+#define MASK_CHAN(x)   ((x) & 3)
+#define MASK_DISABLE   0x04
+
+#define RMASK_CHAN(x)  (1 << ((x) & 3))
+
+#define IS_16BIT(c)    ((c) >= 4)
+
+static void dma_io(int chan, uint32_t phyaddr, int size, unsigned int flags, unsigned int dir);
+static inline void mask(int chan);
+static inline void unmask(int chan);
+
+static int page_port[] = {
+       DMA_0_PAGE, DMA_1_PAGE, DMA_2_PAGE, DMA_3_PAGE,
+       DMA_4_PAGE, DMA_5_PAGE, DMA_6_PAGE, DMA_7_PAGE
+};
+
+void dma_out(int chan, uint32_t phyaddr, int size, unsigned int flags)
+{
+       dma_io(chan, phyaddr, size, flags, MODE_READ);
+}
+
+void dma_in(int chan, uint32_t phyaddr, int size, unsigned int flags)
+{
+       dma_io(chan, phyaddr, size, flags, MODE_WRITE);
+}
+
+static void dma_io(int chan, uint32_t phyaddr, int size, unsigned int flags, unsigned int dir)
+{
+       unsigned int mode;
+       int addr_port, count_port;
+
+       addr_port = DMA_ADDR(chan);
+       count_port = DMA_COUNT(chan);
+
+       mask(chan);
+       outb(0, DMA_CLR_FLIPFLOP(chan));
+
+       /* single / block / cascade */
+       mode = ((flags & 3) << 6) | MODE_CHAN(chan);
+       if(flags & DMA_DECR) mode |= MODE_DECR;
+       if(flags & DMA_AUTO) mode |= MODE_AUTO;
+       outb(mode, DMA_MODE(chan));
+
+       if(IS_16BIT(chan)) {
+               phyaddr >>= 1;
+               size >>= 1;
+       }
+
+       outb(phyaddr & 0xff, addr_port);
+       outb((phyaddr >> 8) & 0xff, addr_port);
+       outb((phyaddr >> 16) & 0xff, page_port[chan]);
+
+       size--;
+       outb(size & 0xff, count_port);
+       outb((size >> 8) & 0xff, count_port);
+
+       unmask(chan);
+}
+
+static inline void mask(int chan)
+{
+       outb(MASK_CHAN(chan) | MASK_DISABLE, DMA_MASK(chan));
+}
+
+static inline void unmask(int chan)
+{
+       outb(MASK_CHAN(chan), DMA_MASK(chan));
+}
diff --git a/src/dma.h b/src/dma.h
new file mode 100644 (file)
index 0000000..8f469c0
--- /dev/null
+++ b/src/dma.h
@@ -0,0 +1,34 @@
+/*
+pcboot - bootable PC demo/game kernel
+Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY, without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef DMA_H_
+#define DMA_H_
+
+#include <inttypes.h>
+
+enum {
+       DMA_SINGLE      = 0x01,
+       DMA_BLOCK       = 0x02,
+       DMA_CASCADE = DMA_SINGLE | DMA_BLOCK,
+       DMA_DECR        = 0x08,
+       DMA_AUTO        = 0x10
+};
+
+void dma_out(int chan, uint32_t phyaddr, int size, unsigned int flags);
+void dma_in(int chan, uint32_t phyaddr, int size, unsigned int flags);
+
+#endif /* DMA_H_ */
index 8e4eba4..8af7cc3 100644 (file)
@@ -49,7 +49,7 @@ void pcboot_main(void)
        /* initialize the timer */
        init_timer();
 
-       init_audio();
+       audio_init();
 
        enable_intr();
 
diff --git a/src/test/ausamples.s b/src/test/ausamples.s
new file mode 100644 (file)
index 0000000..f20b5a9
--- /dev/null
@@ -0,0 +1,6 @@
+       .data
+       .global snd_click
+       .global snd_click_size
+snd_click:
+       .incbin "click-s8_mono.pcm"
+snd_click_size: .long . - snd_click
index 1133dec..186d7a9 100644 (file)
@@ -5,8 +5,10 @@
 #include "keyb.h"
 #include "psaux.h"
 #include "contty.h"
+#include "audio.h"
 
 static void draw_cursor(int x, int y, uint16_t col);
+static int click_sound_callback(void *buffer, int size, void *cls);
 
 static uint16_t *framebuf;
 
@@ -31,10 +33,16 @@ static uint16_t cursor[] = {
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
 };
 
+static int click;
+
+/* defined in sndsamples.s */
+extern void *snd_click;
+extern int snd_click_size;
+
 int vbetest(void)
 {
        int i, j, nmodes, mx, my;
-       unsigned int st;
+       unsigned int st, prev_st = 0;
        struct video_mode vi;
        uint16_t *fbptr;
 
@@ -78,6 +86,8 @@ int vbetest(void)
 
        set_mouse_bounds(0, 0, 639, 479);
 
+       audio_set_callback(click_sound_callback, 0);
+
        /* empty the kb queue */
        while(kb_getkey() != -1);
 
@@ -87,6 +97,20 @@ int vbetest(void)
                }
 
                st = mouse_state(&mx, &my);
+
+               for(i=0; i<3; i++) {
+                       unsigned int bit = 1 << i;
+                       if(((st & bit) ^ (prev_st & bit)) & (st & bit)) {
+                               click = 1;
+                       }
+               }
+               if(click) {
+                       printf("click!\n");
+                       audio_play(22050, 1);
+               }
+
+               prev_st = st;
+
                draw_cursor(mx, my, st & 1 ? 0xf800 : (st & 2 ? 0x7e0 : (st & 4 ? 0x00ff : 0)));
 
                halt_cpu();
@@ -147,3 +171,14 @@ static void draw_cursor(int x, int y, uint16_t col)
                savp += CURSOR_XSZ - w;
        }
 }
+
+/* snd_click_size is < 65536 so we can just throw it all at once in there */
+static int click_sound_callback(void *buffer, int size, void *cls)
+{
+       if(click) {
+               memcpy(buffer, snd_click, snd_click_size);
+               click = 0;
+               return snd_click_size;
+       }
+       return 0;
+}