├── misc └── test.wav ├── source ├── cli.h ├── binary.ld ├── sound.h ├── types.h ├── kernel.h ├── programs │ ├── play.c │ ├── sample.s │ ├── unet.c │ ├── edit.c │ └── nas.c ├── net.h ├── pci.h ├── hwio.h ├── Makefile ├── pci.c ├── syscall.h ├── x86.h ├── load.S ├── boot │ └── boot.s ├── kernel.c ├── fs.h ├── x86.s ├── ulib │ ├── ulib.h │ └── ulib.c ├── cli.c └── sound.c ├── qemu.bat ├── Makefile ├── fstools └── mkfs.c └── README.md /misc/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NANO-DEV/NANO-S32/HEAD/misc/test.wav -------------------------------------------------------------------------------- /source/cli.h: -------------------------------------------------------------------------------- 1 | // Command line interface 2 | 3 | #ifndef _CLI_H 4 | #define _CLI_H 5 | 6 | // Run the integrated CLI 7 | // This function should never return 8 | void cli(); 9 | 10 | // Execute script file 11 | void cli_exec_file(char *path); 12 | 13 | #endif // _CLI_H -------------------------------------------------------------------------------- /source/binary.ld: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .text : 4 | { 5 | *(.text.startup .text.startup.*) 6 | *(.text .stub .text.*) 7 | } 8 | .data : {*(.data)} 9 | .bss : {*(.bss)} 10 | .rodata : {*(.rodata .rodata.* .gnu.linkonce.r.*)} 11 | /DISCARD/ : {*(.eh_frame .note.GNU-stack)} 12 | } -------------------------------------------------------------------------------- /source/sound.h: -------------------------------------------------------------------------------- 1 | // Sound functionality 2 | 3 | #ifndef _SOUND_H 4 | #define _SOUND_H 5 | 6 | // Initialize 7 | void io_sound_init(); 8 | 9 | // True if sound is available 10 | bool io_sound_is_enabled(); 11 | 12 | // Play wav file, non blocking 13 | uint io_sound_play(const char *wav_file); 14 | 15 | // Return true while a sound is playing 16 | bool io_sound_is_playing(); 17 | 18 | // Stop playing sound 19 | void io_sound_stop(); 20 | 21 | #endif // _SOUND_H -------------------------------------------------------------------------------- /source/types.h: -------------------------------------------------------------------------------- 1 | // Data Types - Intel x86 architecture 2 | 3 | #ifndef _TYPES_H 4 | #define _TYPES_H 5 | 6 | // Default data types 7 | typedef unsigned int uint; 8 | typedef unsigned int size_t; 9 | 10 | // Size specific data types 11 | typedef signed char int8_t; 12 | typedef signed short int16_t; 13 | typedef signed int int32_t; 14 | 15 | typedef unsigned char uint8_t; 16 | typedef unsigned short uint16_t; 17 | typedef unsigned int uint32_t; 18 | 19 | // Complex types 20 | typedef struct time_t { 21 | uint year; 22 | uint month; 23 | uint day; 24 | uint hour; 25 | uint minute; 26 | uint second; 27 | } time_t; 28 | 29 | // Null pointer 30 | #define NULL (0) 31 | 32 | typedef _Bool bool; 33 | #define FALSE 0 34 | #define TRUE 1 35 | 36 | #endif // _TYPES_H 37 | -------------------------------------------------------------------------------- /source/kernel.h: -------------------------------------------------------------------------------- 1 | // Kernel 2 | 3 | #ifndef _KERNEL_H 4 | #define _KERNEL_H 5 | 6 | #define OS_VERSION_HI 1 7 | #define OS_VERSION_LO 0 8 | #define OS_BUILD_NUM 14 9 | 10 | // Hardware related disk information is handled by the kernel module. 11 | // File system related information is handled by file system module 12 | #define MAX_DISK 4 13 | 14 | typedef struct disk_info_t { 15 | uint id; // Disk id 16 | char name[4]; // Disk name 17 | char desc[32]; // Desc 18 | uint fstype; // File system type: see ulib.h 19 | uint fssize; // Number of blocks in file system 20 | uint sectors; 21 | uint sides; 22 | uint cylinders; 23 | uint size; // Disk size (MB) 24 | bool isATA; 25 | } disk_info_t; 26 | 27 | extern disk_info_t disk_info[MAX_DISK]; 28 | extern uint8_t system_disk; // System disk 29 | 30 | #endif // _KERNEL_H 31 | -------------------------------------------------------------------------------- /source/programs/play.c: -------------------------------------------------------------------------------- 1 | // User program: Play sound file 2 | 3 | #include "types.h" 4 | #include "ulib/ulib.h" 5 | 6 | // Program entry point 7 | int main(int argc, char *argv[]) 8 | { 9 | // Check args 10 | const bool play_background_mode = 11 | (argc == 3 && strcmp(argv[1],"back")==0); 12 | 13 | if(argc != 2 && !play_background_mode) { 14 | putstr("usage: %s [back] \n", argv[0]); 15 | return 0; 16 | } 17 | 18 | // Play 19 | const uint file_argv_index = argc - 1; 20 | char *file_name = argv[file_argv_index]; 21 | const uint result = sound_play(file_name); 22 | if(result != NO_ERROR) { 23 | putstr("Error: Couldn't play file\n"); 24 | return 1; 25 | } 26 | 27 | // Wait if not in background mode 28 | if(!play_background_mode) { 29 | putstr("Playing %s...", file_name); 30 | while(sound_is_playing()) { 31 | }; 32 | sound_stop(); 33 | 34 | putstr(" Done\n"); 35 | } 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /qemu.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Call with -tap to use tap network device instead of default 3 | 4 | if /i "%~1"=="-tap" goto tap 5 | @echo on 6 | "c:\Program Files\qemu\qemu-system-i386" ^ 7 | -drive file=images\os-fd.img,if=floppy,media=disk,format=raw ^ 8 | -drive file=images\os-hd.img,media=disk,format=raw ^ 9 | -boot c,menu=on -serial mon:stdio -m 2 -vga std ^ 10 | -netdev user,id=u1,net=192.168.2.0/24,dhcpstart=192.168.2.15,hostfwd=udp::8086-:8086 ^ 11 | -device ne2k_pci,netdev=u1 -monitor vc -soundhw sb16 ^ 12 | -object filter-dump,id=f1,netdev=u1,file=dump.dat 13 | @echo off 14 | goto end 15 | :tap 16 | @echo on 17 | "c:\Program Files\qemu\qemu-system-i386" ^ 18 | -drive file=images\os-fd.img,if=floppy,media=disk,format=raw ^ 19 | -drive file=images\os-hd.img,media=disk,format=raw ^ 20 | -boot c,menu=on -serial mon:stdio -m 2 -vga std ^ 21 | -netdev tap,id=u1,ifname=tap -device ne2k_pci,netdev=u1 ^ 22 | -monitor vc -soundhw sb16 ^ 23 | -object filter-dump,id=f1,netdev=u1,file=dump.dat 24 | @echo off 25 | goto end 26 | :end 27 | -------------------------------------------------------------------------------- /source/net.h: -------------------------------------------------------------------------------- 1 | // Network functionality 2 | 3 | #ifndef _NET_H 4 | #define _NET_H 5 | 6 | extern uint8_t local_ip[IP_LEN]; 7 | extern uint8_t local_gate[IP_LEN]; 8 | 9 | // Initialize network 10 | void io_net_init(); 11 | 12 | // Send buffer to dst. Return NO_ERROR on success 13 | uint io_net_send(net_address_t *dst, uint8_t *buff, size_t len); 14 | 15 | // Enable reception in this port and disable all others 16 | // Must be called once before start calling net_recv 17 | void io_net_recv_set_port(uint16_t port); 18 | 19 | // Get and remove from buffer received data. 20 | // src and buff are filled by the function 21 | // Call net_recv_set_port once before calling to this 22 | // function to enable a reception port 23 | uint io_net_recv(net_address_t *src, uint8_t *buff, size_t buff_size); 24 | 25 | enum NET_STATE 26 | { 27 | NET_STATE_DISABLED = 0, 28 | NET_STATE_ENABLED = 1, 29 | NET_STATE_UNINITIALIZED = 0xFFFFFFFF 30 | }; 31 | 32 | // Get network state 33 | // See NET_STATE enum for returned values 34 | uint io_net_get_state(); 35 | 36 | #endif // _NET_H 37 | -------------------------------------------------------------------------------- /source/programs/sample.s: -------------------------------------------------------------------------------- 1 | ; Sample nas program 2 | ; Clear the screen and show "Hello" 3 | 4 | ORG 0x20000 ; Origin address 5 | 6 | main: 7 | push ebx ; save reg state 8 | push ecx 9 | 10 | ; Clear screen syscall 11 | mov ebx, [cls] ; ebx param to syscall: service code 12 | mov ecx, 0x00 ; ecx param to syscall: unused 13 | int 0x31 ; make syscall 14 | 15 | ; Print string 16 | mov ecx, hstr ; Put initial string address in ecx 17 | mov ebx, [ochar] ; out char service code in ebx 18 | 19 | repeat: ; loop for each char 20 | int 0x31 ; make syscall 21 | 22 | add ecx, 4 ; inc ecx so it points next char of str 23 | mov edx, 0 24 | cmp [ecx], edx ; compare current char and 0 25 | jne repeat ; if not equal, continue loop 26 | 27 | ; Otherwise, we are done 28 | pop ecx ; Pop prevously pushed params 29 | pop ebx 30 | 31 | ret ; Finish 32 | 33 | ; "Hello\n" string (ascii) 34 | hstr dd 0x48, 0x65, 0x6C, 0x6C, 0x6F, 10, 0 35 | 36 | ; syscall codes 37 | cls dd 0x22 ; clear screen 38 | ochar dd 0x20 ; out char 39 | -------------------------------------------------------------------------------- /source/pci.h: -------------------------------------------------------------------------------- 1 | // PCI 2 | 3 | #ifndef _PCI_H 4 | #define _PCI_H 5 | 6 | // Header type 0x00, general device 7 | typedef struct PCI_device_t { 8 | uint16_t vendor_id; 9 | uint16_t device_id; 10 | uint16_t command; 11 | uint16_t status; 12 | uint8_t revison_id; 13 | uint8_t prog_if; 14 | uint8_t subclass; 15 | uint8_t class_code; 16 | uint8_t cache_line_size; 17 | uint8_t latency_timer; 18 | uint8_t header_type; 19 | uint8_t bist; 20 | uint32_t bar0; 21 | uint32_t bar1; 22 | uint32_t bar2; 23 | uint32_t bar3; 24 | uint32_t bar4; 25 | uint32_t bar5; 26 | uint32_t cardbus_cis_pointer; 27 | uint16_t subsystem_vendor_id; 28 | uint16_t subsustem_id; 29 | uint32_t eROM_base_addr; 30 | uint8_t capabilities_pointer; 31 | uint8_t reserved[7]; 32 | uint8_t interrput_line; 33 | uint8_t interrput_pin; 34 | uint8_t min_grant; 35 | uint8_t max_latency; 36 | } PCI_device_t; 37 | 38 | // Initialize PCI 39 | void pci_init(); 40 | 41 | // Find device 42 | PCI_device_t *pci_find_device(uint16_t vendor, uint16_t device); 43 | 44 | 45 | #endif // _PCI_H 46 | -------------------------------------------------------------------------------- /source/hwio.h: -------------------------------------------------------------------------------- 1 | // Hardware and basic IO 2 | 3 | #ifndef _HWIO_H 4 | #define _HWIO_H 5 | 6 | // Get key press 7 | enum IO_GETKEY_WAITMODE{ 8 | IO_GETKEY_WAITMODE_NOWAIT = 0, 9 | IO_GETKEY_WAITMODE_WAIT 10 | }; 11 | uint io_getkey(uint wait_mode); 12 | 13 | // VGA text mode 14 | void io_vga_putc(char c, uint8_t attr); 15 | void io_vga_putc_attr(uint x, uint y, char c, uint8_t attr); 16 | void io_vga_clear(); 17 | void io_vga_getcursorpos(uint *x, uint *y); 18 | void io_vga_setcursorpos(uint x, uint y); 19 | void io_vga_showcursor(bool show); 20 | 21 | // Put char serial 22 | void io_serial_putc(char c); 23 | 24 | // Get time 25 | void io_getdatetime(time_t *time); 26 | uint io_gettimer(); 27 | 28 | // Return NO_ERROR on success 29 | #define DISK_SECTOR_SIZE 512 // hardware sector size in bytes 30 | void io_disks_init_info(); 31 | uint io_disk_read(uint disk, uint sector, uint offset, size_t size, void *buff); 32 | uint io_disk_write(uint disk, uint sector, uint offset, size_t size, const void *buff); 33 | 34 | // Init lapic, including timer 35 | void lapic_init(); 36 | void lapic_eoi(); // Acknowledge 37 | void set_network_IRQ(uint irq); // Set IRQ for network driver 38 | void set_sound_IRQ(uint irq); // Set IRQ for sound driver 39 | 40 | // Shutdown computer 41 | void apm_shutdown(); 42 | 43 | // Enable/disable interrupts 44 | void enable_interrupts(); 45 | void disable_interrupts(); 46 | 47 | #endif // _HWIO_H 48 | -------------------------------------------------------------------------------- /source/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | BOOTDIR := boot/ 4 | ULIBDIR := ulib/ 5 | PROGDIR := programs/ 6 | 7 | CC = gcc 8 | LD = ld 9 | NASM = nasm 10 | CFLAGS := -std=c99 -fno-pic -static -fno-builtin -nostdinc -fno-strict-aliasing -O2 -Wall -m32 -MD -Wextra -fno-omit-frame-pointer -fno-stack-protector 11 | LDFLAGS := -melf_i386 --oformat binary 12 | 13 | KERNELOBJS = load.o x86.o cli.o hwio.o kernel.o pci.o fs.o net.o sound.o $(ULIBDIR)ulib.o 14 | 15 | all: $(BOOTDIR)boot.bin kernel.n32 programs 16 | 17 | programs: $(PROGDIR)play.bin $(PROGDIR)edit.bin $(PROGDIR)nas.bin $(PROGDIR)unet.bin 18 | 19 | $(PROGDIR)%.bin: $(PROGDIR)%.c $(ULIBDIR)ulib.o $(ULIBDIR)ulib.h types.h 20 | $(CC) $(CFLAGS) -I. -o $(PROGDIR)$*.o -c $(PROGDIR)$*.c 21 | $(LD) $(LDFLAGS) -N -e main -Ttext 0x20000 -T binary.ld -o $@ $(PROGDIR)$*.o $(ULIBDIR)ulib.o 22 | 23 | $(BOOTDIR)boot.bin: $(BOOTDIR)boot.s 24 | $(NASM) -O0 -w+orphan-labels -f bin -o $@ $(BOOTDIR)boot.s 25 | 26 | -include *.d 27 | 28 | kernel.n32: $(KERNELOBJS) 29 | $(LD) $(LDFLAGS) -N -Ttext 0x8000 -T binary.ld -o $@ $(KERNELOBJS) 30 | 31 | load.o: load.S 32 | $(CC) $(CFLAGS) -o $@ -c load.S 33 | 34 | $(ULIBDIR)ulib.o: 35 | $(CC) $(CFLAGS) -I. -o $@ -c $(ULIBDIR)ulib.c 36 | 37 | x86.o: x86.s 38 | $(NASM) -O0 -w+orphan-labels -f elf -o $@ x86.s 39 | 40 | %.o: %.c 41 | $(CC) $(CFLAGS) -o $@ -c $< 42 | 43 | clean: 44 | @find . -name "*.o" -type f -delete 45 | @find . -name "*.d" -type f -delete 46 | @find . -name "*.bin" -type f -delete 47 | @find . -name "*.n32" -type f -delete 48 | 49 | .PHONY: all programs clean 50 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # Directories 4 | IMAGEDIR := images/ 5 | SOURCEDIR := source/ 6 | FSTOOLSDIR := fstools/ 7 | MISCDIR := misc/ 8 | 9 | # User files and args for mkfs 10 | USERFILES := $(SOURCEDIR)programs/play.bin $(SOURCEDIR)programs/edit.bin \ 11 | $(SOURCEDIR)programs/nas.bin $(SOURCEDIR)programs/sample.s \ 12 | $(SOURCEDIR)programs/unet.bin $(MISCDIR)test.wav 13 | MKFSARGS := $(SOURCEDIR)boot/boot.bin $(SOURCEDIR)kernel.n32 $(USERFILES) 14 | 15 | # Make source and create images 16 | all: $(FSTOOLSDIR)mkfs 17 | $(MAKE) $@ -C $(SOURCEDIR) --no-print-directory 18 | mkdir -p $(IMAGEDIR) 19 | $(FSTOOLSDIR)mkfs $(IMAGEDIR)os-fd.img 2880 $(MKFSARGS) 20 | $(FSTOOLSDIR)mkfs $(IMAGEDIR)os-hd.img 28800 $(MKFSARGS) 21 | 22 | # mkfs generates disk images 23 | $(FSTOOLSDIR)mkfs: $(FSTOOLSDIR)mkfs.c $(SOURCEDIR)fs.h 24 | gcc -Werror -Wall -I$(SOURCEDIR) -o $(FSTOOLSDIR)mkfs $(FSTOOLSDIR)mkfs.c 25 | 26 | # run in emulators 27 | # Specify QEMU path here 28 | QEMU = qemu-system-i386 29 | 30 | QEMUOPTS = -drive file=$(IMAGEDIR)os-fd.img,if=floppy,media=disk,format=raw \ 31 | -drive file=$(IMAGEDIR)os-hd.img,media=disk,format=raw -d guest_errors \ 32 | -boot c,menu=on -serial mon:stdio -m 2 -vga std -monitor vc -soundhw sb16 \ 33 | -netdev user,id=u1,net=192.168.2.0/24,dhcpstart=192.168.2.15,hostfwd=udp::8086-:8086 \ 34 | -device ne2k_pci,netdev=u1 35 | 36 | qemu: all 37 | $(QEMU) $(QEMUOPTS) 38 | 39 | qemu_no_rebuild: 40 | $(QEMU) $(QEMUOPTS) 41 | 42 | # Clean 43 | clean: 44 | rm -f $(FSTOOLSDIR)mkfs 45 | rm -f $(IMAGEDIR)os-fd.img $(IMAGEDIR)os-hd.img 46 | $(MAKE) $@ -C $(SOURCEDIR) --no-print-directory 47 | @find . -name "*.dat" -type f -delete 48 | 49 | .PHONY: all ctags qemu clean 50 | -------------------------------------------------------------------------------- /source/pci.c: -------------------------------------------------------------------------------- 1 | // PCI 2 | 3 | #include "types.h" 4 | #include "x86.h" 5 | #include "pci.h" 6 | #include "ulib/ulib.h" 7 | 8 | #define PCI_CONFIG_ADDR_PORT 0xCF8 9 | #define PCI_CONFIG_DATA_PORT 0xCFC 10 | 11 | #define MAX_PCI_DEVICE 16 12 | static uint pci_count = 0; 13 | static PCI_device_t pci_devices[MAX_PCI_DEVICE]; 14 | 15 | // Read config 16 | static uint32_t pci_read_config(uint32_t pci_dev, uint8_t offset) 17 | { 18 | const uint32_t data = 0x80000000 | pci_dev | (offset & 0xFC); 19 | outd(PCI_CONFIG_ADDR_PORT, data); 20 | return ind(PCI_CONFIG_DATA_PORT); 21 | } 22 | 23 | // Scan devices in bus 0 24 | void pci_init() 25 | { 26 | // Initialize PCI table 27 | memset(pci_devices, 0, sizeof(pci_devices)); 28 | 29 | // Scan bus 0 devices 30 | const uint8_t bus = 0; 31 | pci_count = 0; 32 | for(uint8_t slot=0; slot<32; slot++) { 33 | for(uint8_t func=0; func<8; func++) { 34 | uint32_t pci_dev_addr = 35 | ((bus<<16) | (slot<<11) | (func<<8)); 36 | 37 | uint16_t vendor_id = pci_read_config(pci_dev_addr, 0)&0xFFFF; 38 | 39 | // Discard unused 40 | if(vendor_id == 0xFFFF) { 41 | continue; 42 | } 43 | 44 | // Read device 45 | uint32_t *p = (uint32_t *)&pci_devices[pci_count]; 46 | 47 | for(uint i=0; i= MAX_PCI_DEVICE) { 53 | debug_putstr("There are unlisted PCI devices\n"); 54 | return; 55 | } 56 | } 57 | } 58 | 59 | // Print debug info 60 | debug_putstr("PCI initialized\n"); 61 | for(uint i=0; i> 12) & 0xffff), ((base) & 0xffff); \ 19 | .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ 20 | (bits | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) 21 | 22 | #define SBITS_16 0x00 23 | #define SBITS_32 0xC0 24 | 25 | #define STA_X 0x8 // Executable segment 26 | #define STA_E 0x4 // Expand down (non-executable segments) 27 | #define STA_C 0x4 // Conforming code segment (executable only) 28 | #define STA_W 0x2 // Writeable (non-executable segments) 29 | #define STA_R 0x2 // Readable (executable segments) 30 | #define STA_A 0x1 // Accessed 31 | 32 | #define CR0_PE 0x00000001 // Protection Enable 33 | 34 | #define SEG_KCODE 1 // kernel code 35 | #define SEG_KDATA 2 // kernel data+stack 36 | 37 | 38 | # Loader starts here 39 | 40 | .code16 # Assemble for 16-bit mode 41 | .globl start, system_hwdisk 42 | .section .text.startup 43 | start: 44 | cli # BIOS enabled interrupts; disable 45 | 46 | # Zero data segment registers DS, ES, and SS 47 | xorw %ax,%ax # Set %ax to zero 48 | movw %ax,%ds # -> Data Segment 49 | movw %ax,%es # -> Extra Segment 50 | movw %ax,%ss # -> Stack Segment 51 | 52 | movb %dl,system_hwdisk # save boot disk as system disk 53 | 54 | movw $0x1111,%ax # Set font 55 | movb $0, %bl 56 | int $0x10 57 | 58 | # Enable A20 line 59 | movw $0x2401,%ax # A20-Gate Activate by BIOS 60 | int $0x15 61 | 62 | # Switch from real to protected mode. Use a flat GDT that makes 63 | # virtual addresses map directly to physical addresses so that the 64 | # effective memory map doesn't change during the transition 65 | lgdt gdtdesc 66 | movl %cr0, %eax 67 | orl $CR0_PE, %eax 68 | movl %eax, %cr0 69 | 70 | # Complete the transition to 32-bit protected mode by using a long jmp 71 | # to reload %cs and %eip. The segment descriptors are set up with no 72 | # translation, so that the mapping is still the identity mapping 73 | ljmp $(SEG_KCODE<<3), $start32 74 | 75 | .section .text 76 | .code32 # Tell assembler to generate 32-bit code now 77 | start32: 78 | # Set up the protected-mode data segment registers 79 | movw $(SEG_KDATA<<3), %ax # Data segment selector 80 | movw %ax, %ds # -> DS: Data Segment 81 | movw %ax, %es # -> ES: Extra Segment 82 | movw %ax, %ss # -> SS: Stack Segment 83 | movw $0, %ax # Zero segments not ready for use 84 | movw %ax, %fs # -> FS 85 | movw %ax, %gs # -> GS 86 | 87 | # Set up the stack pointer and call C function 88 | movl $start, %esp 89 | call kernel 90 | 91 | # If kernel returns (it shouldn't), loop 92 | spin: 93 | jmp spin 94 | 95 | system_hwdisk: 96 | .byte 0 97 | 98 | # Bootstrap GDT 99 | .p2align 2 # force 4 byte alignment 100 | gdt: 101 | SEG_NULLASM # null seg 102 | SEG_ASM(SBITS_32,STA_X|STA_R, 0x0, 0xffffffff) # code seg 103 | SEG_ASM(SBITS_32,STA_W, 0x0, 0xffffffff) # data seg 104 | SEG_ASM(SBITS_16,STA_X|STA_R, 0x0, 0xffffffff) # code seg 105 | SEG_ASM(SBITS_16,STA_W, 0x0, 0xffffffff) # data seg 106 | 107 | gdtdesc: 108 | .word (gdtdesc - gdt - 1) # sizeof(gdt) - 1 109 | .long gdt # address gdt 110 | -------------------------------------------------------------------------------- /source/boot/boot.s: -------------------------------------------------------------------------------- 1 | ; Boot Record for BIOS-based PCs 2 | 3 | ; Code location constants 4 | %define ORG_LOC 0x7C00 ; Initial MBR position in memory 5 | %define STAGE_LOC 0x8000 ; Location of stage 6 | %define BDISK_LOC 0x0660 ; Location to store boot disk in memory 7 | 8 | [ORG ORG_LOC] 9 | [BITS 16] 10 | jmp 0x0000:start 11 | 12 | ; Start of the bootstrap code 13 | start: 14 | ; Set up a stack 15 | mov ax, 0 16 | 17 | cli ; Disable interrupts while changing stack 18 | mov ss, ax 19 | mov ds, ax 20 | mov es, ax 21 | mov sp, ORG_LOC 22 | sti 23 | 24 | .setup_data: 25 | mov [BDISK_LOC], dl 26 | 27 | mov ah, 8 ; Get disk parameters 28 | int 0x13 29 | jc error 30 | and cx, 0x3F ; Maximum sector number 31 | mov [SECTORS], cx ; Sector numbers start at 1 32 | movzx dx, dh ; Maximum head number 33 | add dx, 1 ; Head numbers start at 0 - add 1 for total 34 | mov [SIDES], dx 35 | 36 | mov eax, 0 37 | mov ax, 1 38 | 39 | mov bx, (STAGE_LOC>>4) 40 | mov es, bx 41 | 42 | .read_next: 43 | push ax 44 | call disk_lba_to_hts 45 | 46 | mov bx, [BUFFER] 47 | 48 | mov ah, 2 49 | mov al, 1 50 | 51 | pusha 52 | 53 | .read_loop: 54 | popa 55 | pusha 56 | 57 | stc ; Some BIOSes do not set properly on error 58 | int 0x13 ; Read sectors 59 | 60 | jnc .read_finished 61 | call disk_reset ; Reset controller and try again 62 | jnc .read_loop ; Disk reset OK? 63 | 64 | popa 65 | jmp error ; Fatal double error 66 | 67 | .read_finished: 68 | popa ; Restore registers from main loop 69 | pop ax 70 | cmp ax, 1 71 | jne .inc_loop ; If it was the super block 72 | mov bx, [BUFFER] 73 | mov ax, [es:bx+12] 74 | mov bx, ax 75 | add bx, 127 76 | mov [LASTBLOCK], bx 77 | jmp .read_next ; find where the bootable image starts 78 | .inc_loop: 79 | inc ax 80 | mov bx, [BUFFER] 81 | add bx, 512 82 | mov [BUFFER], bx 83 | cmp ax, [LASTBLOCK] 84 | jne .read_next 85 | 86 | ; Jump to stage 87 | mov dl, [BDISK_LOC] 88 | jmp (STAGE_LOC>>4):0x0000 89 | 90 | ; Reset disk 91 | disk_reset: 92 | push ax 93 | push dx 94 | mov ax, 0 95 | mov dl, byte [BDISK_LOC] 96 | stc 97 | int 0x13 98 | pop dx 99 | pop ax 100 | ret 101 | 102 | ; disk_lba_to_hts -- Calculate head, track and sector for int 0x13 103 | ; IN: logical sector in AX; OUT: correct registers for int 0x13 104 | disk_lba_to_hts: 105 | push bx 106 | push ax 107 | 108 | mov bx, ax ; Save logical sector 109 | 110 | mov dx, 0 ; First the sector 111 | div word [SECTORS] ; Sectors per track 112 | add dl, 0x01 ; Physical sectors start at 1 113 | mov cl, dl ; Sectors belong in CL for int 0x13 114 | mov ax, bx 115 | 116 | mov dx, 0 ; Now calculate the head 117 | div word [SECTORS] ; Sectors per track 118 | mov dx, 0 119 | div word [SIDES] ; Disk sides 120 | mov dh, dl ; Head/side 121 | mov ch, al ; Track 122 | 123 | pop ax 124 | pop bx 125 | 126 | mov dl, [BDISK_LOC] ; Set disk 127 | 128 | ret 129 | 130 | SECTORS dw 18 131 | SIDES dw 2 132 | BUFFER dw 0 133 | LASTBLOCK dw 240 134 | 135 | error: 136 | mov si, disk_error ; If not, print error message 137 | call print_string 138 | jmp error 139 | 140 | print_string: ; Output string in SI to screen 141 | pusha 142 | mov ah, 0x0E ; int 10h teletype function 143 | 144 | .repeat: 145 | lodsb ; Get char from string 146 | cmp al, 0 147 | je .done ; If char is zero, end of string 148 | int 0x10 ; Otherwise, print it 149 | jmp short .repeat 150 | 151 | .done: 152 | popa 153 | ret 154 | 155 | disk_error db "Disk error", 0 156 | 157 | ; ------------------------------------------------------------------ 158 | ; END OF BOOT SECTOR 159 | 160 | times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros 161 | dw 0xAA55 ; Boot signature 162 | -------------------------------------------------------------------------------- /fstools/mkfs.c: -------------------------------------------------------------------------------- 1 | // Creates a system disk image 2 | // 3 | // This program runs only on development environment, 4 | // so architecture can differ from target architecture 5 | // 6 | // Expected parameters: 7 | // output_file block_count kernel [other files] 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define MKFS // fs.h needs this 17 | 18 | // Define these types for development machine arch 19 | // fs.h also needs this 20 | typedef char uint8_t; 21 | typedef unsigned int uint32_t; 22 | 23 | #include "fs.h" 24 | 25 | #define min(a,b) (ab?a:b) 27 | 28 | // Output file descriptor 29 | int fsfd; 30 | 31 | // Write and read blocks 32 | void wblock(uint, void*); 33 | void rblock(uint sec, void *buf); 34 | 35 | // Entry point 36 | int main(int argc, char *argv[]) 37 | { 38 | int i, f, e, b, cc, fd; 39 | char *name; 40 | char buf[BLOCK_SIZE]; 41 | sfs_superblock_t sfs_sb; 42 | sfs_entry_t *sfs_entry; 43 | 44 | // Check architecture and fs definition sizes 45 | assert(sizeof(uint8_t) == 1); 46 | assert(sizeof(uint32_t) == 4); 47 | assert(BLOCK_SIZE % sizeof(sfs_entry_t) == 0 || 48 | sizeof(sfs_entry_t) % BLOCK_SIZE == 0); 49 | 50 | // Check usage 51 | if(argc < 5) { 52 | fprintf(stderr, 53 | "Usage: %s output_file fs_size_blocks boot_sect kernel_file [other_files ...]\n", 54 | argv[0]); 55 | 56 | exit(1); 57 | } 58 | 59 | // Get fs parameters 60 | int fssize_blocks = atoi(argv[2]); // Size of file system in blocks 61 | int numentries = min(((fssize_blocks * BLOCK_SIZE)/10)/sizeof(sfs_entry_t), 4096); 62 | int entries_size = numentries * sizeof(sfs_entry_t); 63 | 64 | // Open output file 65 | fsfd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0666); 66 | if(fsfd < 0) { 67 | perror(argv[1]); 68 | exit(1); 69 | } 70 | 71 | // Create empty boot block 72 | memset(buf, 0, sizeof(buf)); 73 | 74 | // Now add boot image to the block 75 | if((fd = open(argv[3], 0)) < 0) { 76 | perror(argv[3]); 77 | exit(1); 78 | } 79 | 80 | read(fd, buf, 512); 81 | close(fd); 82 | wblock(0, buf); 83 | 84 | // Write all image with 0s 85 | memset(buf, 0, sizeof(buf)); 86 | for(i = 1; i < fssize_blocks; i++) 87 | wblock(i, buf); 88 | 89 | // Superblock 90 | sfs_sb.type = SFS_TYPE_ID; 91 | sfs_sb.size = fssize_blocks; 92 | sfs_sb.nentries = numentries; 93 | sfs_sb.bootstart = 2 + entries_size/BLOCK_SIZE; 94 | 95 | memmove(buf, &sfs_sb, sizeof(sfs_sb)); 96 | wblock(1, buf); 97 | 98 | printf("%s: creating %s (size=%d nentries=%d bootstart=%d)\n", 99 | argv[0], argv[1], sfs_sb.size, sfs_sb.nentries, sfs_sb.bootstart); 100 | 101 | // Create empty entries table 102 | sfs_entry = malloc(entries_size); 103 | memset(sfs_entry, 0, entries_size); 104 | e = 0; 105 | b = sfs_sb.bootstart; 106 | 107 | // Create root dir 108 | strncpy(sfs_entry[e].name, ROOT_DIR_NAME, SFS_NAMESIZE-1); 109 | sfs_entry[e].flags = T_DIR; 110 | sfs_entry[e].time = 0; 111 | sfs_entry[e].size = argc - 4; 112 | sfs_entry[e].parent = 0; 113 | sfs_entry[e].next = 0; 114 | 115 | e++; 116 | 117 | // Copy input files to image 118 | // The first one is expected to be the kernel 119 | for(f = 4; f < argc; f++) { 120 | 121 | // Open file 122 | if((fd = open(argv[f], 0)) < 0) { 123 | perror(argv[f]); 124 | exit(1); 125 | } 126 | 127 | // Remove slashes from name 128 | name = strrchr(argv[f], '/'); 129 | if(name == 0) { 130 | name = argv[f]; 131 | } else { 132 | name++; 133 | } 134 | 135 | // Create first file entry 136 | sfs_entry[0].ref[f - 4] = e; 137 | 138 | strncpy(sfs_entry[e].name, name, SFS_NAMESIZE-1); 139 | sfs_entry[e].flags = T_FILE; 140 | sfs_entry[e].time = 0; 141 | sfs_entry[e].size = 0; 142 | sfs_entry[e].parent = 0; 143 | sfs_entry[e].next = 0; 144 | 145 | 146 | // Read file, write data block, and add more entries 147 | // when there is no room for more refs in current entry 148 | i = 0; 149 | 150 | while((cc = read(fd, buf, BLOCK_SIZE)) > 0) { 151 | int ec = e + (sfs_entry[e].size / (SFS_ENTRYREFS*BLOCK_SIZE)); 152 | if(e != ec && sfs_entry[ec].name[0] == 0) { 153 | strncpy(sfs_entry[ec].name, sfs_entry[e].name, SFS_NAMESIZE-1); 154 | sfs_entry[ec-1].next = ec; 155 | sfs_entry[ec].flags = T_FILE; 156 | sfs_entry[ec].time = 0; 157 | sfs_entry[ec].size = 0; 158 | sfs_entry[ec].parent = ec-1; 159 | sfs_entry[ec].next = 0; 160 | i = 0; 161 | } 162 | 163 | sfs_entry[ec].ref[i] = b; 164 | wblock(sfs_entry[ec].ref[i], buf); 165 | while(ec >= e) { 166 | sfs_entry[ec].size += cc; 167 | ec--; 168 | } 169 | i++; 170 | b++; 171 | } 172 | 173 | close(fd); 174 | 175 | while(sfs_entry[e].name[0]) { 176 | e++; 177 | } 178 | } 179 | 180 | // Write entries table 181 | if(lseek(fsfd, 2*BLOCK_SIZE, 0) != 2*BLOCK_SIZE) { 182 | perror("lseek"); 183 | exit(1); 184 | } 185 | 186 | if(write(fsfd, sfs_entry, entries_size) != entries_size) { 187 | perror("write"); 188 | exit(1); 189 | } 190 | 191 | // Done! Free memory, close file, and exit 192 | free(sfs_entry); 193 | close(fsfd); 194 | 195 | exit(0); 196 | } 197 | 198 | // Write block 199 | void wblock(uint bindex, void *buf) 200 | { 201 | if(lseek(fsfd, bindex*BLOCK_SIZE, 0) != bindex*BLOCK_SIZE) { 202 | perror("lseek"); 203 | exit(1); 204 | } 205 | if(write(fsfd, buf, BLOCK_SIZE) != BLOCK_SIZE) { 206 | perror("write"); 207 | exit(1); 208 | } 209 | } 210 | 211 | // Read block 212 | void rblock(uint bindex, void *buf) 213 | { 214 | if(lseek(fsfd, bindex*BLOCK_SIZE, 0) != bindex*BLOCK_SIZE) { 215 | perror("lseek"); 216 | exit(1); 217 | } 218 | if(read(fsfd, buf, BLOCK_SIZE) != BLOCK_SIZE) { 219 | perror("read"); 220 | exit(1); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /source/programs/unet.c: -------------------------------------------------------------------------------- 1 | // User program: Basic net interface 2 | 3 | #include "types.h" 4 | #include "ulib/ulib.h" 5 | 6 | // Use this arbitrarily chosen UDP port 7 | #define UNET_PORT 8086 8 | 9 | // Program called with recv argument 10 | static int unet_recv() 11 | { 12 | // Set reception port 13 | recv_set_port(UNET_PORT); 14 | 15 | // Receive in buffer 16 | char recv_buff[64] = {0}; 17 | net_address_t src_addr; 18 | const uint result = 19 | recv(&src_addr, (uint8_t*)recv_buff, sizeof(recv_buff)); 20 | 21 | // If result == 0, nothing has been received 22 | if(result == 0) { 23 | putstr("Buffer is empty\n"); 24 | 25 | // Else, result contains number of received bytes 26 | } else { 27 | // Append a 0 after received data 28 | // to safely use as string 29 | recv_buff[min(result, sizeof(recv_buff)-1)] = 0; 30 | 31 | // Show source address and contents 32 | putstr("Received %s from %u.%u.%u.%u:%u\n", recv_buff, 33 | src_addr.ip[0], src_addr.ip[1], 34 | src_addr.ip[2], src_addr.ip[3], 35 | src_addr.port); 36 | } 37 | return (int)result; 38 | } 39 | 40 | 41 | // Program called with send argument 42 | static int unet_send(net_address_t *dst_addr, char *message) 43 | { 44 | // Send 45 | const uint result = 46 | send(dst_addr, (uint8_t*)message, strlen(message)+1); 47 | 48 | // If result == 0, then data was sent 49 | if(result == NO_ERROR) { 50 | putstr("Sent %s to %u.%u.%u.%u:%u\n", message, 51 | dst_addr->ip[0], dst_addr->ip[1], 52 | dst_addr->ip[2], dst_addr->ip[3], 53 | dst_addr->port); 54 | } else { 55 | // Else, failed 56 | putstr("Failed to send\n"); 57 | } 58 | 59 | return (int)result; 60 | } 61 | 62 | 63 | 64 | 65 | 66 | // Section: Chat related functions 67 | // ******************************* 68 | 69 | // Auxiliar function for unet_chat: Clear current screen line 70 | static void unet_chat__clear_current_line() 71 | { 72 | const uint SCREEN_WIDTH = 80; 73 | putstr("\r"); 74 | for(uint i=0; iip, sizeof(recv_addr.ip)) == 0 && 91 | recv_addr.port == UNET_PORT) { 92 | 93 | // Append a 0 after received data 94 | // to safely use as string 95 | recv_msg[min(result, sizeof(recv_msg)-1)] = 0; 96 | 97 | // Go to the beginning of the current line 98 | unet_chat__clear_current_line(); 99 | 100 | // Print source address and message 101 | putstr("%d.%d.%d.%d: %s\n", 102 | recv_addr.ip[0], recv_addr.ip[1], 103 | recv_addr.ip[2], recv_addr.ip[3], 104 | recv_msg); 105 | } 106 | } 107 | 108 | // Auxiliar function for unet_chat: process local input 109 | // Return UNET_CHAT_EXIT if chat must finish 110 | // Return UNET_CHAT_CONTINUE otherwise 111 | #define UNET_CHAT_CONTINUE 0 112 | #define UNET_CHAT_EXIT 1 113 | static uint unet_chat__process_local_input( 114 | net_address_t *remote_chat_addr, 115 | char *send_msg_buff, size_t send_msg_buff_size) 116 | { 117 | // Process keyboard input (don't wait for a keypress) 118 | const uint pressed_key = getkey(GETKEY_WAITMODE_NOWAIT); 119 | 120 | // If key RETURN: Send 121 | if(pressed_key == KEY_RETURN) { 122 | // Set a 0 at the end of the buffer 123 | // to safely use as string 124 | send_msg_buff[send_msg_buff_size-1] = 0; 125 | 126 | if(strlen(send_msg_buff) > 0) { 127 | const uint result = send(remote_chat_addr, 128 | (uint8_t*)send_msg_buff, strlen(send_msg_buff)); 129 | 130 | unet_chat__clear_current_line(); 131 | 132 | // If result == 0, then data was sent 133 | if(result == NO_ERROR) { 134 | putstr("local: %s\n", send_msg_buff); 135 | } else { 136 | // Else, failed 137 | putstr("Failed to send message\n"); 138 | } 139 | } 140 | 141 | // Reset buffer 142 | memset(send_msg_buff, 0, send_msg_buff_size); 143 | } 144 | 145 | // If key BACKSPACE or DEL: Delete last char 146 | else if((pressed_key == KEY_BACKSPACE || pressed_key == KEY_DEL) && 147 | strlen(send_msg_buff) > 0) { 148 | send_msg_buff[strlen(send_msg_buff)-1] = 0; 149 | 150 | // Rewrite buffer in screen 151 | unet_chat__clear_current_line(); 152 | putstr("%s", send_msg_buff); 153 | } 154 | 155 | // If key is a char: Append it to current user string 156 | else if(pressed_key >= ' ' && pressed_key <= '}') { 157 | const uint current_char_index = 158 | min(send_msg_buff_size-2, strlen(send_msg_buff)); 159 | 160 | // Note in the previous computation of current_char_index 161 | // Maximum index to append a char is send_msg_buff_size-2 162 | // because send_msg_buff_size-1 must be always 0 163 | // to ensure send_msg_buff contains a valid string 164 | 165 | send_msg_buff[current_char_index] = pressed_key; 166 | unet_chat__clear_current_line(); 167 | putstr("%s", send_msg_buff); 168 | } 169 | 170 | // If key is KEY_ESC: Exit chat 171 | else if(pressed_key == KEY_ESC) { 172 | // Acknowledge ESC key pressed and return with exit value 173 | unet_chat__clear_current_line(); 174 | putstr("-ESC-\n"); 175 | return UNET_CHAT_EXIT; 176 | } 177 | 178 | // Unless key ESC is pressed, chat must continue 179 | return UNET_CHAT_CONTINUE; 180 | } 181 | 182 | 183 | // Program called with chat argument: Main chat function 184 | static int unet_chat(net_address_t *remote_addr) 185 | { 186 | // Show chat banner 187 | putstr("Chat with %u.%u.%u.%u. Press ESC to exit\n", 188 | remote_addr->ip[0], remote_addr->ip[1], 189 | remote_addr->ip[2], remote_addr->ip[3]); 190 | 191 | // Set reception port 192 | recv_set_port(UNET_PORT); 193 | 194 | // Initialize send message buffer 195 | char send_msg_buff[256] = {0}; 196 | memset(send_msg_buff, 0, sizeof(send_msg_buff)); 197 | 198 | // Main loop 199 | uint user_command = 0; 200 | do { 201 | // Show received messages, if any 202 | unet_chat__receive(remote_addr); 203 | 204 | // Process local user input, if any 205 | user_command = unet_chat__process_local_input( 206 | remote_addr, send_msg_buff, sizeof(send_msg_buff)); 207 | 208 | } while(user_command != UNET_CHAT_EXIT); 209 | 210 | // Exit chat 211 | return 0; 212 | } 213 | 214 | 215 | // End of section: Chat related functions 216 | // ************************************** 217 | 218 | 219 | 220 | 221 | 222 | // Program entry point 223 | int main(int argc, char *argv[]) 224 | { 225 | // Switch functionality depending on call params 226 | if(argc == 2 && strcmp(argv[1], "recv") == 0) { 227 | 228 | // Syntax: 229 | // unet recv 230 | // Show received data 231 | 232 | return unet_recv(); 233 | 234 | } else if(argc == 5 && strcmp(argv[1], "send") == 0) { 235 | 236 | // Syntax: 237 | // unet send 238 | // Send single word to IP:port 239 | 240 | // Parse IP from string 241 | net_address_t dst_addr; 242 | str_to_ip(dst_addr.ip, argv[2]); 243 | dst_addr.port = stou(argv[3]); 244 | 245 | return unet_send(&dst_addr, argv[4]); 246 | 247 | } else if(argc == 3 && strcmp(argv[1], "chat") == 0) { 248 | 249 | // Syntax: 250 | // unet chat 251 | // Start a chat with a remote host (remote_IP) 252 | 253 | // Parse IP from string 254 | net_address_t remote_chat_addr; 255 | str_to_ip(remote_chat_addr.ip, argv[2]); 256 | remote_chat_addr.port = UNET_PORT; 257 | 258 | return unet_chat(&remote_chat_addr); 259 | 260 | } else { 261 | // Call syntax not recognized 262 | // Print usage 263 | putstr("usage: %s | recv | chat >\n", 264 | argv[0]); 265 | } 266 | 267 | return 0; 268 | } 269 | -------------------------------------------------------------------------------- /source/kernel.c: -------------------------------------------------------------------------------- 1 | // Kernel 2 | 3 | #include "types.h" 4 | #include "kernel.h" 5 | #include "hwio.h" 6 | #include "ulib/ulib.h" 7 | #include "fs.h" 8 | #include "syscall.h" 9 | #include "x86.h" 10 | #include "pci.h" 11 | #include "cli.h" 12 | #include "net.h" 13 | #include "sound.h" 14 | 15 | uint8_t system_disk = 0xFF; 16 | disk_info_t disk_info[MAX_DISK]; 17 | 18 | // Heap related 19 | static const void *HEAPADDR = (void*)0x30000; // Allocate heap memory here 20 | #define HEAP_NUM_BLOCK 0x00020 21 | #define HEAP_MEM_SIZE 0x40000 22 | #define HEAP_BLOCK_SIZE (HEAP_MEM_SIZE / HEAP_NUM_BLOCK) 23 | 24 | typedef struct heap_block_t { 25 | bool used; 26 | void *ptr; 27 | } heap_block_t; 28 | static heap_block_t heap[HEAP_NUM_BLOCK]; 29 | 30 | // Init heap: all blocks are unused 31 | static void heap_init() 32 | { 33 | for(uint i=0; i= n_alloc) { 61 | uint bi = (i+1-n_alloc)*HEAP_BLOCK_SIZE; 62 | void *addr = (void*)HEAPADDR + bi; 63 | for(uint j=i+1-n_alloc; j<=i; j++) { 64 | heap[j].ptr = addr; 65 | heap[j].used = TRUE; 66 | } 67 | debug_putstr("heap: found at %x\n", addr); 68 | return addr; 69 | } 70 | } 71 | } 72 | 73 | // Error: not found 74 | debug_putstr("Mem alloc: BAD ALLOC (%u bytes)\n", size); 75 | return NULL; 76 | } 77 | 78 | // Free memory in heap 79 | static void heap_free(const void *ptr) 80 | { 81 | if(ptr != NULL) { 82 | for(uint i=0; i> 8); 115 | char c = (uparam & 0xFF); 116 | io_vga_putc(c, attr); 117 | return 0; 118 | } 119 | 120 | case SYSCALL_IO_OUT_CHAR_ATTR: { 121 | syscall_posattr_t *pc = param; 122 | io_vga_putc_attr(pc->x, pc->y, pc->c, pc->attr); 123 | return 0; 124 | } 125 | 126 | case SYSCALL_IO_CLEAR_SCREEN: { 127 | io_vga_clear(); 128 | return 0; 129 | } 130 | 131 | case SYSCALL_IO_SET_CURSOR_POS: { 132 | syscall_porition_t *ps = param; 133 | io_vga_setcursorpos(ps->x, ps->y); 134 | return 0; 135 | } 136 | 137 | case SYSCALL_IO_GET_CURSOR_POS: { 138 | syscall_porition_t *ps = param; 139 | io_vga_getcursorpos(&ps->x, &ps->y); 140 | return 0; 141 | } 142 | 143 | case SYSCALL_IO_SET_SHOW_CURSOR: { 144 | io_vga_showcursor(*(bool*)param); 145 | return 0; 146 | } 147 | 148 | case SYSCALL_IO_IN_KEY: { 149 | const uint wait_mode = 150 | *(uint*)param == GETKEY_WAITMODE_WAIT ? 151 | IO_GETKEY_WAITMODE_WAIT : 152 | IO_GETKEY_WAITMODE_NOWAIT; 153 | return io_getkey(wait_mode); 154 | } 155 | 156 | case SYSCALL_IO_OUT_CHAR_SERIAL: 157 | case SYSCALL_IO_OUT_CHAR_DEBUG: { 158 | io_serial_putc(*(char*)param); 159 | return 0; 160 | } 161 | 162 | case SYSCALL_FS_GET_INFO: { 163 | syscall_fsinfo_t *fi = param; 164 | return fs_get_info(fi->disk_index, fi->info); 165 | } 166 | 167 | case SYSCALL_FS_GET_ENTRY: { 168 | syscall_fsentry_t *fi = param; 169 | sfs_entry_t entry; 170 | fs_entry_t o_entry; 171 | char path[MAX_PATH] = {0}; 172 | memcpy(path, fi->path, sizeof(path)); 173 | uint result = fs_get_entry(&entry, path, fi->parent, fi->disk); 174 | memcpy(o_entry.name, entry.name, sizeof(o_entry.name)); 175 | o_entry.flags = entry.flags; 176 | o_entry.size = entry.size; 177 | memcpy(fi->entry, &o_entry, sizeof(o_entry)); 178 | return result; 179 | } 180 | 181 | case SYSCALL_FS_READ_FILE: { 182 | syscall_fsrwfile_t *fi = param; 183 | char path[MAX_PATH] = {0}; 184 | memcpy(path, fi->path, sizeof(path)); 185 | return fs_read_file(fi->buff, path, fi->offset, fi->count); 186 | } 187 | 188 | case SYSCALL_FS_WRITE_FILE: { 189 | syscall_fsrwfile_t *fi = param; 190 | char path[MAX_PATH] = {0}; 191 | memcpy(path, fi->path, sizeof(path)); 192 | return fs_write_file(fi->buff, path, fi->offset, fi->count, fi->flags); 193 | } 194 | 195 | case SYSCALL_FS_MOVE: { 196 | syscall_fssrcdst_t *fi = param; 197 | char src[MAX_PATH] = {0}; 198 | char dst[MAX_PATH] = {0}; 199 | memcpy(src, fi->src, sizeof(src)); 200 | memcpy(dst, fi->dst, sizeof(dst)); 201 | return fs_move(src, dst); 202 | } 203 | 204 | case SYSCALL_FS_COPY: { 205 | syscall_fssrcdst_t *fi = param; 206 | char src[MAX_PATH] = {0}; 207 | char dst[MAX_PATH] = {0}; 208 | memcpy(src, fi->src, sizeof(src)); 209 | memcpy(dst, fi->dst, sizeof(dst)); 210 | return fs_copy(src, dst); 211 | } 212 | 213 | case SYSCALL_FS_DELETE: { 214 | char path[MAX_PATH] = {0}; 215 | memcpy(path, param, sizeof(path)); 216 | return fs_delete(path); 217 | } 218 | 219 | case SYSCALL_FS_CREATE_DIRECTORY: { 220 | char path[MAX_PATH] = {0}; 221 | memcpy(path, param, sizeof(path)); 222 | return fs_create_directory(path); 223 | } 224 | 225 | case SYSCALL_FS_LIST: { 226 | syscall_fslist_t *fi = param; 227 | sfs_entry_t entry; 228 | fs_entry_t o_entry; 229 | char path[MAX_PATH] = {0}; 230 | memcpy(path, fi->path, sizeof(path)); 231 | uint result = fs_list(&entry, path, fi->n); 232 | memcpy(o_entry.name, entry.name, sizeof(o_entry.name)); 233 | o_entry.flags = entry.flags; 234 | o_entry.size = entry.size; 235 | memcpy(fi->entry, &o_entry, sizeof(o_entry)); 236 | return result; 237 | } 238 | 239 | case SYSCALL_FS_FORMAT: { 240 | return fs_format(*(uint*)param); 241 | } 242 | 243 | case SYSCALL_DATETIME_GET: { 244 | io_getdatetime(param); 245 | return 0; 246 | } 247 | 248 | case SYSCALL_TIMER_GET: { 249 | return io_gettimer(); 250 | } 251 | 252 | case SYSCALL_NET_RECV: { 253 | syscall_netop_t *no = param; 254 | return io_net_recv(no->addr, no->buff, no->size); 255 | } 256 | 257 | case SYSCALL_NET_SEND: { 258 | syscall_netop_t *no = param; 259 | return io_net_send(no->addr, no->buff, no->size); 260 | } 261 | 262 | case SYSCALL_NET_PORT: { 263 | uint16_t *port = (uint16_t*)param; 264 | io_net_recv_set_port(*port); 265 | return 0; 266 | } 267 | 268 | case SYSCALL_SOUND_PLAY: { 269 | return io_sound_play((const char*)param); 270 | } 271 | 272 | case SYSCALL_SOUND_STOP: { 273 | io_sound_stop(); 274 | return 0; 275 | } 276 | 277 | case SYSCALL_SOUND_IS_PLAYING: { 278 | return io_sound_is_playing() ? TRUE : FALSE; 279 | } 280 | }; 281 | 282 | return 0; 283 | } 284 | 285 | // The main kernel function 286 | void kernel() 287 | { 288 | // Install ISR 289 | install_ISR(); 290 | 291 | debug_putstr("nano32 %u.%u build %u\n", 292 | OS_VERSION_HI, OS_VERSION_LO, OS_BUILD_NUM); 293 | 294 | // Init heap 295 | heap_init(); 296 | 297 | // Init LAPIC 298 | lapic_init(); 299 | 300 | // Init disks info 301 | io_disks_init_info(); 302 | 303 | // Init FS info 304 | fs_init_info(); 305 | 306 | // Print current disk 307 | debug_putstr("system disk: %2x\n", 308 | system_disk); 309 | 310 | // Initialize PCI 311 | pci_init(); 312 | 313 | // Initialize network 314 | io_net_init(); 315 | 316 | // Initialize sound 317 | io_sound_init(); 318 | 319 | // Execute config file 320 | cli_exec_file("config.ini"); 321 | 322 | // Show starting message 323 | putstr("Starting...\n"); 324 | 325 | // Run CLI 326 | // This function should never return 327 | cli(); 328 | 329 | // Something went wrong 330 | // Just spin 331 | while(1) { 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /source/fs.h: -------------------------------------------------------------------------------- 1 | // File system 2 | 3 | #ifndef _FS_H 4 | #define _FS_H 5 | 6 | // File system specification 7 | 8 | // This file system provides files, directories, pathnames and 9 | // bootloading facilities 10 | 11 | // The file system divides disk space into logical blocks of contiguous space. 12 | // The size of blocks need not be the same size as the sector size of the disk 13 | // the file system resides on 14 | #define BLOCK_SIZE 512 // block size in bytes 15 | // With the current implementation, BLOCK_SIZE must be a power of 2 16 | 17 | // Disk layout: 18 | // [boot block | super block | entries table | data blocks] 19 | 20 | // Boot block block 0 Boot sector 21 | // Super block block 1 Contains information about the layout of the file system 22 | // Entries tab blocks 2 to n Table of file and directory entries 23 | // Data blocks blocks n to end Data blocks referenced by file entries 24 | 25 | // Entries are referenced by their index on the entry table 26 | // Entry with index n is located at byte: 27 | // 2*BLOCK_SIZE + n*sizeof(sfs_entry_t) 28 | // 29 | // Data blocks start at block 30 | // 2 + ((superblock.nentries*sizeof(sfs_entry_t)) / BLOCK_SIZE) 31 | // 32 | // Data blocks are referenced by their absolute disk block index 33 | 34 | // SFS 1.0 ID used in superblock.type 35 | #define SFS_TYPE_ID 0x05F50010 36 | 37 | typedef struct sfs_superblock_t { // On-disk superblock structure 38 | uint32_t type; // Type of file system. Must be SFS_TYPE_ID 39 | uint32_t size; // Total number of block in file system 40 | uint32_t nentries; // Number of entries in entries table 41 | uint32_t bootstart; // Block index of first boot program block 42 | } sfs_superblock_t; 43 | 44 | // The boot program must be stored in contiguous data blocks 45 | 46 | #define SFS_NAMESIZE 15 // Max length of entry name + final 0 47 | #define SFS_ENTRYREFS 120 // Number of references in a single entry 48 | 49 | // Entry flags 50 | #define T_DIR 0x01 // Type: Directory 51 | #define T_FILE 0x02 // Type: File 52 | 53 | #define F_USED (T_DIR | T_FILE) // Not a flag! Used to find free entries 54 | // ( (entry flags & F_USED) == 0 ) means this is a free entry 55 | 56 | // fs time format: 57 | // bits 0-21 second of month 58 | // bits 22-31 months since 2017/01/01 00:00:00 59 | 60 | typedef struct sfs_entry_t { // On-disk entry structure 61 | uint8_t flags; // Entry flags. See above 62 | uint8_t name[SFS_NAMESIZE]; // Entry name, must be finished with a 0 63 | uint32_t time; // Last modification date, see format above 64 | uint32_t size; // File size (bytes) or number of items in a dir 65 | uint32_t parent; // Parent dir entry, or previous chained entry 66 | uint32_t next; // Next chained entry index. See below 67 | uint32_t ref[SFS_ENTRYREFS]; // References to blocks or entries. See below 68 | } sfs_entry_t; 69 | 70 | // With the current implementation, sizeof(sfs_entry_t) must be a power of 2 71 | 72 | // References in file entries contain ordered data block indexes 73 | // References in directory entries contain subentries indexes 74 | // 75 | // A reference with value 0 means unused reference 76 | // All used references must be always packed before unused references 77 | // 78 | // Chained entries: 79 | // When more references than those a single entry can fit are needed, 80 | // entry.next contains the index of another entry for the same file or directory 81 | // (a chained entry) whose references are concatenated to the previous 82 | // entry ones. Chained entries have the same flags, name and time than their 83 | // head entry (the first one). An entry.next with value 0 means no more chained 84 | // entries 85 | // 86 | // Size in file entries contains file size in bytes 87 | // Size in directory entries contains the number of items in this directory 88 | // In both cases, size refers to the remaining size starting from the current 89 | // chained entry. So, the total size is only in the head entry of a file 90 | // or directory. 91 | // 92 | // entry.parent of the head entry of a file or directory contains the 93 | // index of its parent directory. 94 | // entry.parent of an entry which is not the first entry of a file or 95 | // directory (a chained entry), contains a reference to the previous entry in 96 | // the chain. 97 | // 98 | // The root dir of a disk is always entry index 0, with name "." and parent 0. 99 | // With the current implementation, the boot program must be entry index 1. 100 | 101 | #define ROOT_DIR_NAME "." 102 | #define PATH_SEPARATOR '/' 103 | #define PATH_SEPARATOR_S "/" 104 | 105 | // If MKFS is defined, the following decalrations are unneeded 106 | #ifndef MKFS 107 | 108 | // Unless another thing is specified, all paths must be provided as 109 | // absolute or relative to the system disk. 110 | // When provided as absolute, they must begin with a disk identifier. 111 | // Possible disk identifiers are stored in disk_info[].name. Usually: 112 | // fd0 - First floppy disk 113 | // fd1 - Second floppy disk 114 | // hd0 - First hard disk 115 | // hd1 - Second hard disk 116 | // 117 | // Path components are separated with PATH_SEPARATOR ('/') 118 | // The root directory of a disk can be omitted or referred as 119 | // ROOT_DIR_NAME (".") 120 | // 121 | // Examples of valid paths: 122 | // fd0 123 | // hd0/. 124 | // hd0/documents/file.txt 125 | // programs/edit.bin 126 | 127 | // When disks must be referenced by uint disk, valid values are 128 | // disk_info[] indices. Usually: 129 | // fd0 : 0x00 130 | // fd1 : 0x01 131 | // hd0 : 0x02 132 | // hd1 : 0x03 133 | 134 | // Init filesystem info 135 | // Call this to update internal file system related disk info 136 | void fs_init_info(); 137 | 138 | // Get filesystem info 139 | // Output: info 140 | // disk refers to the index on currently available disks list. 141 | // returns number of available disks 142 | uint fs_get_info(uint disk, fs_info_t *info); 143 | 144 | // Get filesystem entry 145 | // Output: entry 146 | // parent and/or disk can be UNKNOWN_VALUE (see ulib) if they are unknown. 147 | // In this case they will be deducted from path 148 | // Paths must be: 149 | // - absolute or relative to system disk if parent and/or disk are unknown 150 | // - relative to parent if parent index entry or disk id are provided 151 | // Returns ERROR_NOT_FOUND if error, entry index otherwise 152 | uint fs_get_entry(sfs_entry_t *entry, char *path, uint parent, uint disk); 153 | 154 | // Read file 155 | // Output: buff 156 | // Reads count bytes of path file starting at byte offset inside this file. 157 | // Returns number of readed bytes or ERROR_NOT_FOUND 158 | uint fs_read_file(void *buff, char *path, uint offset, size_t count); 159 | 160 | // Write file flags 161 | #define WF_CREATE 0x0001 // Create file if it does not exist 162 | #define WF_TRUNCATE 0x0002 // Truncate file to the last written position 163 | 164 | // Write file 165 | // Writes count bytes of path file starting at byte offset inside this file. 166 | // If target file is not big enough, its size is increased. 167 | // Depending on flags, path file can be created or truncated. 168 | // Returns number of written bytes or ERROR_NOT_FOUND 169 | uint fs_write_file(const void *buff, char *path, uint offset, size_t count, uint flags); 170 | 171 | // Move entry 172 | // In the case of directories, they are recursively moved 173 | // Returns: 174 | // - ERROR_NOT_FOUND if source does not exist 175 | // - ERROR_EXISTS if destination exists 176 | // - another value otherwise 177 | uint fs_move(char *srcpath, char *dstpath); 178 | 179 | // Copy entry 180 | // In the case of directories, they are recursively copied 181 | // Returns: 182 | // - ERROR_NOT_FOUND if source does not exist 183 | // - ERROR_EXISTS if destination exists 184 | // - another value otherwise 185 | uint fs_copy(char *srcpath, char *dstpath); 186 | 187 | // Delete entry 188 | // In the case of directories, they are recursively deleted 189 | // Returns: 190 | // - ERROR_NOT_FOUND if path does not exist 191 | // - index of deleted entry otherwise 192 | uint fs_delete(char *path); 193 | 194 | // Create a directory 195 | // Returns: 196 | // - ERROR_NOT_FOUND if parent path does not exist 197 | // - ERROR_EXISTS if destination already exists 198 | // - index of created entry otherwise 199 | uint fs_create_directory(char *path); 200 | 201 | // List directory entries 202 | // Output: entry 203 | // Gets entry with index n in path directory 204 | // Returns: 205 | // - ERROR_NOT_FOUND if path does not exist 206 | // - number of elements in ths directory otherwise 207 | uint fs_list(sfs_entry_t *entry, char *path, uint n); 208 | 209 | // Create filesystem in disk 210 | // Deletes all files, creates NSFS filesystem 211 | // and adds a copy of the kernel 212 | // Returns NO_ERROR on success 213 | uint fs_format(uint disk); 214 | 215 | // Convert fs time to system time_t 216 | // See fs time format specification above 217 | uint fs_fstime_to_systime(uint32_t fst, time_t *syst); 218 | 219 | // Convert system time_t to fs time 220 | // See fs time format specification above 221 | uint32_t fs_systime_to_fstime(time_t *syst); 222 | 223 | // Auxiliar functions 224 | const char *disk_to_string(uint disk); 225 | uint string_to_disk(const char *str); 226 | uint blocks_to_MB(uint blocks); 227 | 228 | // Debug functions 229 | uint fs_print_map(char *filename); 230 | 231 | #endif // MKFS 232 | 233 | #endif // _FS_H 234 | -------------------------------------------------------------------------------- /source/x86.s: -------------------------------------------------------------------------------- 1 | ; Several x86 utils 2 | 3 | [bits 32] 4 | 5 | ; void dump_regs() 6 | ; Dump register values through debug output 7 | global dump_regs 8 | dump_regs: 9 | pushad 10 | 11 | push cs 12 | push ds 13 | push es 14 | push ss 15 | 16 | push .dumpstr 17 | call debug_putstr 18 | pop eax 19 | 20 | pop eax 21 | pop eax 22 | pop eax 23 | pop eax 24 | 25 | popad 26 | ret 27 | 28 | .dumpstr db "REG DUMP:",10,"SS=%x ES=%x DS=%x CS=%x",10,"DI=%x SI=%x BP=%x SP=%x",10,"BX=%x DX=%x CX=%x AX=%x",10,0 29 | extern debug_putstr 30 | 31 | struc regs16_t 32 | .di resw 1 33 | .si resw 1 34 | .bp resw 1 35 | .sp resw 1 36 | .bx resw 1 37 | .dx resw 1 38 | .cx resw 1 39 | .ax resw 1 40 | .gs resw 1 41 | .fs resw 1 42 | .es resw 1 43 | .ds resw 1 44 | .ef resw 1 45 | endstruc 46 | 47 | k16_stack: 48 | times regs16_t_size db 0 ; kernel16 stack 49 | k16_stack_top: 50 | 51 | %define GDTENTRY(x) ((x) << 3) 52 | %define CODE32 GDTENTRY(1) ; 0x08 53 | %define DATA32 GDTENTRY(2) ; 0x10 54 | %define CODE16 GDTENTRY(3) ; 0x18 55 | %define DATA16 GDTENTRY(4) ; 0x20 56 | 57 | ; int32 call makes its POSSIBLE to execute BIOS interrupts 58 | ; by temporally switching to real mode 59 | ; 60 | ; Adapted from original work by Napalm (thank you!) 61 | ; License: http://creativecommons.org/licenses/by-sa/2.0/uk/ 62 | ; 63 | ; Notes: int32() resets all selectors 64 | ; void _cdelc int32(uint8_t intnum, regs16_t *regs); 65 | ; 66 | 67 | extern lapic_inhibit, lapic_deinhibit 68 | extern enable_interrupts, disable_interrupts 69 | 70 | global int32 71 | int32: use32 72 | call disable_interrupts 73 | pushfd 74 | pushad ; save register state to 32bit stack 75 | call lapic_inhibit ; inhibit LAPIC interrupts 76 | cld ; clear direction flag (copy forward) 77 | mov [stack32_ptr], esp ; save 32bit stack pointer 78 | lea esi, [esp+0x28] ; set position of intnum on 32bit stack 79 | lodsd ; read intnum into eax 80 | mov [ib], al ; set interrupt immediate byte from arguments 81 | mov esi, [esi] ; read regs pointer in esi as source 82 | mov edi, k16_stack ; set destination to 16bit stack 83 | mov ecx, regs16_t_size ; set copy size to struct size 84 | mov esp, edi ; save destination to 16bit stack offset 85 | rep movsb ; copy 32bit stack to 16bit stack 86 | jmp word CODE16:p_mode16 ; switch to 16bit protected mode 87 | p_mode16: use16 88 | mov ax, DATA16 ; get 16bit data selector 89 | mov ds, ax ; set ds to 16bit selector 90 | mov es, ax ; set es to 16bit selector 91 | mov fs, ax ; set fs to 16bit selector 92 | mov gs, ax ; set gs to 16bit selector 93 | mov ss, ax ; set ss to 16bit selector 94 | mov eax, cr0 ; get cr0 so it can be modified 95 | and al, ~0x01 ; mask off PE bit to turn off protected mode 96 | mov cr0, eax ; set cr0 to result 97 | jmp word 0x0000:r_mode16 ; set cs:ip to enter real-mode 98 | r_mode16: use16 99 | mov ax, 0 ; set ax to zero 100 | mov ds, ax ; set ds to access idt16 101 | mov ss, ax ; set ss so valid stack 102 | lidt [idt16_ptr] ; load 16bit idt 103 | popa ; load general purpose registers from 16bit stack 104 | pop gs ; load gs from 16bit stack 105 | pop fs ; load fs from 16bit stack 106 | pop es ; load es from 16bit stack 107 | pop ds ; load ds from 16bit stack 108 | mov sp, [stack32_ptr] ; set usable sp 109 | push ax 110 | mov al, 0x00 ; unmask PIC interrupts 111 | out 0x21, al 112 | out 0xA1, al 113 | pop ax 114 | db 0xCD ; opcode of INT instruction with immediate byte 115 | ib: db 0x00 116 | push ax 117 | mov al, 0xFF ; mask PIC interrupts 118 | out 0x21, al 119 | out 0xA1, al 120 | pop ax 121 | mov sp, 0 ; zero sp so it can be reused 122 | mov ss, sp ; set ss so the stack is valid 123 | mov sp, k16_stack_top ; set correct stack position to copy 124 | pushf ; save eflags to 16bit stack 125 | push ds ; save ds to 16bit stack 126 | push es ; save es to 16bit stack 127 | push fs ; save fs to 16bit stack 128 | push gs ; save gs to 16bit stack 129 | pusha ; save general purpose registers to 16bit stack 130 | mov sp, [stack32_ptr] ; set usable sp 131 | mov eax, cr0 ; get cr0 so it can be modified 132 | or al, 0x01 ; set PE bit to turn on protected mode 133 | mov cr0, eax ; set cr0 to result 134 | jmp dword CODE32:p_mode32 ; switch to 32bit selector (32bit protected mode) 135 | p_mode32: use32 136 | mov ax, DATA32 ; get 32bit data selector 137 | mov ds, ax ; reset ds selector 138 | mov es, ax ; reset es selector 139 | mov fs, ax ; reset fs selector 140 | mov gs, ax ; reset gs selector 141 | mov ss, ax ; reset ss selector 142 | lidt [idtr] ; restore 32bit idt pointer 143 | mov esp, [stack32_ptr] ; restore 32bit stack pointer 144 | mov esi, k16_stack ; set copy source to 16bit stack 145 | lea edi, [esp+0x2C] ; set position of regs pointer on 32bit stack 146 | mov edi, [edi] ; use regs pointer in edi as copy destination 147 | mov ecx, regs16_t_size ; set copy size to struct size 148 | cld ; clear direction flag (copy forward) 149 | rep movsb ; copy (16bit stack to 32bit stack) 150 | call lapic_deinhibit ; dehinibit LAPIC interrupts 151 | popad ; restore registers 152 | popfd 153 | call enable_interrupts 154 | ret ; return to caller 155 | 156 | 157 | stack32_ptr: ; address in 32bit stack after 158 | dd 0x00000000 ; saving all general purpose registers 159 | 160 | idt16_ptr: ; IDT table pointer for 16bit access 161 | dw 0x03FF ; table limit (size) 162 | dd 0x00000000 ; table base address 163 | 164 | global idtr 165 | idtr: 166 | dw (64*8)-1 167 | global pidt 168 | pidt: 169 | dd idt 170 | 171 | idt: 172 | times 64*8 db 0 173 | 174 | 175 | ; IRQ handler wrappers 176 | extern timer_handler 177 | IRQ0_wrapper: 178 | pushad 179 | call timer_handler 180 | popad 181 | iret 182 | 183 | extern spurious_handler 184 | IRQ31_wrapper: 185 | pushad 186 | call spurious_handler 187 | popad 188 | iret 189 | 190 | extern net_handler 191 | global IRQNet_wrapper 192 | IRQNet_wrapper: 193 | pushad 194 | call net_handler 195 | popad 196 | iret 197 | 198 | extern sound_handler 199 | global IRQSound_wrapper 200 | IRQSound_wrapper: 201 | pushad 202 | call sound_handler 203 | popad 204 | iret 205 | 206 | ; Install interrupt handler 207 | global install_ISR 208 | install_ISR: use32 209 | pushad 210 | 211 | ; Configure PIC 212 | mov al, 0x11 ; 0x11 = ICW1_INIT | ICW1_ICW4 213 | out 0x20, al ; send ICW1 to master pic 214 | out 0xA0, al ; send ICW1 to slave pic 215 | mov al, 0x08 ; get master pic vector param 216 | out 0x21, al ; send ICW2 aka vector to master pic 217 | mov al, 0x70 ; get slave pic vector param 218 | out 0xA1, al ; send ICW2 aka vector to slave pic 219 | mov al, 0x04 ; 0x04 = set slave to IRQ2 220 | out 0x21, al ; send ICW3 to master pic 221 | mov al, 0x02 ; 0x02 = tell slave its on IRQ2 of master 222 | out 0xA1, al ; send ICW3 to slave pic 223 | mov al, 0x01 ; 0x01 = ICW4_8086 224 | out 0x21, al ; send ICW4 to master pic 225 | out 0xA1, al ; send ICW4 to slave pic 226 | 227 | mov al, 0xFF ; mask PIC interrupts 228 | out 0x21, al 229 | out 0xA1, al 230 | 231 | ; Setup IDT 232 | 233 | ; Syscall 234 | mov eax, int_handler 235 | mov [idt+49*8], ax 236 | mov word [idt+49*8+2], CODE32 237 | mov word [idt+49*8+4], 0x8F00 238 | shr eax, 16 239 | mov [idt+49*8+6], ax 240 | 241 | ; IRQ0 (timer) 242 | mov eax, IRQ0_wrapper 243 | mov [idt+32*8], ax 244 | mov word [idt+32*8+2], CODE32 245 | mov word [idt+32*8+4], 0x8E00 246 | shr eax, 16 247 | mov [idt+32*8+6], ax 248 | 249 | ; IRQ31 (spurious) 250 | mov eax, IRQ31_wrapper 251 | mov [idt+63*8], ax 252 | mov word [idt+63*8+2], CODE32 253 | mov word [idt+63*8+4], 0x8E00 254 | shr eax, 16 255 | mov [idt+63*8+6], ax 256 | 257 | ; Load IDT 258 | lidt [idtr] 259 | 260 | popad 261 | ret 262 | 263 | ; Interrupt int_handler 264 | ; Call c function 265 | int_handler: use32 266 | pushad 267 | push ecx 268 | push ebx 269 | call kernel_service 270 | mov [.result], eax 271 | pop eax 272 | pop eax 273 | popad 274 | mov eax, [.result] 275 | iretd 276 | 277 | .result dd 0 ; Result of ISR 278 | 279 | ALIGN 512 280 | global disk_buff 281 | disk_buff: 282 | times 512 db 0 283 | 284 | extern kernel_service 285 | -------------------------------------------------------------------------------- /source/ulib/ulib.h: -------------------------------------------------------------------------------- 1 | // User library 2 | 3 | #ifndef _ULIB_H 4 | #define _ULIB_H 5 | 6 | #define min(a,b) (ab?a:b) 8 | 9 | // System call 10 | // Avoid using it since there are already implemented 11 | // more general purpose functions in this library 12 | uint syscall(uint service, void *param); 13 | 14 | // Memory 15 | void *memset(void *dst, int c, size_t n); 16 | size_t memcpy(void *dst, const void *src, size_t n); 17 | size_t memcmp(const void *mem1, const void *mem2, size_t n); 18 | 19 | // String management 20 | size_t strcmp(const char *str1, const char *str2); 21 | size_t strlen(const char *str); 22 | size_t strncpy(char *dst, const char *src, size_t n); 23 | size_t strncat(char *dst, const char *src, size_t n); 24 | size_t strchr(const char *str, char c); 25 | char *strtok(char *src, char **next, char delim); 26 | 27 | // strtok: 28 | // Get first token in string 29 | // 30 | // src is the input untokenized string. It will be modified. 31 | // delim is the delimiter character to separate tokens. 32 | // The function returns a pointer to the start of first token in src. 33 | // All consecutive delimiters found immediately after the first token 34 | // will be replaced with 0, so return value is a 0 finished string with 35 | // the first token. 36 | // After execution, (*next) will point to the remaining string after the 37 | // first token and its final 0 (or 0s). 38 | // 39 | // To tokenize a full string, this function should be called several times, 40 | // until *next = 0 41 | 42 | // Parse string uint 43 | uint stou(char *src); 44 | 45 | // Format a string using %x, %d, %u, %U, %s... 46 | void formatstr(char *str, size_t size, char *format, ...); 47 | 48 | 49 | // Allocate size bytes of memory 50 | void *malloc(size_t size); 51 | 52 | // Free allocated memory 53 | void mfree(void *ptr); 54 | 55 | 56 | // Cursor management 57 | void get_cursor_pos(uint *col, uint *row); 58 | void set_cursor_pos(uint col, uint row); 59 | void set_show_cursor(bool show); 60 | 61 | 62 | // Special key codes 63 | // 64 | // getkey() function returns a key code (uint) 65 | // Alphanumeric and usual symbol key codes are their ASCII code. 66 | // Special key values are specified here. 67 | // Example: 68 | // 69 | // uint k = getkey(); 70 | // if(k == KEY_DEL) ... 71 | // if(k == 'a') ... 72 | 73 | #define KEY_BACKSPACE 0x08 74 | #define KEY_RETURN 0x0D 75 | #define KEY_ESC 0x1B 76 | #define KEY_DEL 0xE0 77 | #define KEY_END 0xE1 78 | #define KEY_HOME 0xE2 79 | #define KEY_INS 0xE3 80 | #define KEY_PG_DN 0xE4 81 | #define KEY_PG_UP 0xE5 82 | #define KEY_PRT_SC 0xE6 83 | #define KEY_TAB 0x09 84 | 85 | #define KEY_UP 0xE7 86 | #define KEY_LEFT 0xE8 87 | #define KEY_RIGHT 0xE9 88 | #define KEY_DOWN 0xEA 89 | 90 | #define KEY_F1 0xF1 91 | #define KEY_F2 0xF2 92 | #define KEY_F3 0xF3 93 | #define KEY_F4 0xF4 94 | #define KEY_F5 0xF5 95 | #define KEY_F6 0xF6 96 | #define KEY_F7 0xF7 97 | #define KEY_F8 0xF8 98 | #define KEY_F9 0xF9 99 | #define KEY_F10 0xFA 100 | #define KEY_F11 0xFB 101 | #define KEY_F12 0xFC 102 | 103 | enum GETKEY_WAITMODE{ 104 | GETKEY_WAITMODE_NOWAIT = 0, // Return 0 if no key pressed 105 | GETKEY_WAITMODE_WAIT // Wait for key pressed 106 | }; 107 | uint getkey(uint wait_mode); // See GETKEY_WAITMODE enum 108 | int getstr(char *str, size_t n); 109 | // getstr: Get a string from user. Returns when RETURN key is 110 | // pressed. Unused str characters are set to 0. 111 | // Returns number of elements in str 112 | 113 | // Attribute flags for function putc_attr 114 | // AT_T_ defines text color 115 | // AT_B_ defines background color 116 | #define AT_T_BLACK 0x00 117 | #define AT_T_BLUE 0x01 118 | #define AT_T_GREEN 0x02 119 | #define AT_T_CYAN 0x03 120 | #define AT_T_RED 0x04 121 | #define AT_T_MAGENTA 0x05 122 | #define AT_T_BROWN 0x06 123 | #define AT_T_LGRAY 0x07 124 | #define AT_T_DGRAY 0x08 125 | #define AT_T_LBLUE 0x09 126 | #define AT_T_LGREEN 0x0A 127 | #define AT_T_LCYAN 0x0B 128 | #define AT_T_LRED 0x0C 129 | #define AT_T_LMAGENTA 0x0D 130 | #define AT_T_YELLOW 0x0E 131 | #define AT_T_WHITE 0x0F 132 | 133 | #define AT_B_BLACK 0x00 134 | #define AT_B_BLUE 0x10 135 | #define AT_B_GREEN 0x20 136 | #define AT_B_CYAN 0x30 137 | #define AT_B_RED 0x40 138 | #define AT_B_MAGENTA 0x50 139 | #define AT_B_BROWN 0x60 140 | #define AT_B_LGRAY 0x70 141 | #define AT_B_DGRAY 0x80 142 | #define AT_B_LBLUE 0x90 143 | #define AT_B_LGREEN 0xA0 144 | #define AT_B_LCYAN 0xB0 145 | #define AT_B_LRED 0xC0 146 | #define AT_B_LMAGENTA 0xD0 147 | #define AT_B_YELLOW 0xE0 148 | #define AT_B_WHITE 0xF0 149 | 150 | // Put char and put formatted string in screen 151 | void putc(char c); 152 | void putc_attr(uint col, uint row, char c, uint8_t attr); 153 | void clear_screen(); 154 | 155 | // Put char and put formatted string in screen 156 | // Supports: 157 | // %d (int), %u (uint), %x (uint), 158 | // %s (char*), %c (uchar) 159 | // Width modifiers allowed: %2d, %4x... 160 | void putstr(const char *format, ...); 161 | 162 | // Formatted strings in serial port and debug output. 163 | // Debug output is serial port by default 164 | void serial_putstr(const char *format, ...); 165 | void debug_putstr(const char *format, ...); 166 | 167 | 168 | 169 | // Get current system date and time 170 | void get_datetime(time_t *t); 171 | 172 | // Get current system timer (miliseconds) 173 | uint get_timer(); 174 | 175 | // Wait a number of miliseconds 176 | void wait(uint ms); 177 | 178 | 179 | // File system related 180 | 181 | // Unless another thing is specified, all paths must be provided as 182 | // absolute or relative to the system disk. 183 | // When specified as absolute, they must begin with a disk identifier. 184 | // Possible disk identifiers are: 185 | // fd0 - First floppy disk 186 | // fd1 - Second floppy disk 187 | // hd0 - First hard disk 188 | // hd1 - Second hard disk 189 | // 190 | // Path components are separated with slashes '/' 191 | // The root directory of a disk can be omitted or referred as "." 192 | // when it's the last path component. 193 | // 194 | // Examples of valid paths: 195 | // fd0 196 | // hd0/. 197 | // hd0/documents/file.txt 198 | // 199 | 200 | // When disks must be referenced by uint disk, valid values are: 201 | // fd0 : 0x00 202 | // fd1 : 0x01 203 | // hd0 : 0x80 204 | // hd1 : 0x81 205 | 206 | // Special error codes 207 | #define ERROR_NOT_FOUND 0xFFFFFFFF 208 | #define ERROR_EXISTS 0xFFFFFFFE 209 | #define ERROR_IO 0xFFFFFFFD 210 | #define ERROR_NO_SPACE 0xFFFFFFFC 211 | #define ERROR_NOT_AVAILABLE 0xFFFFFFFB 212 | #define ERROR_ANY 0xFFFFFFFA 213 | #define NO_ERROR 0x00000000 214 | 215 | // fs_entry_t flags 216 | #define FST_DIR 0x01 // Directory 217 | #define FST_FILE 0x02 // File 218 | 219 | typedef struct fs_entry_t { 220 | char name[15]; 221 | uint8_t flags; 222 | uint size; // bytes for files, items for directories 223 | } fs_entry_t; 224 | 225 | #define MAX_PATH 72 226 | 227 | // fs_info_t.fs_type types 228 | #define FS_TYPE_UNKNOWN 0x000 229 | #define FS_TYPE_NSFS 0x001 230 | 231 | typedef struct fs_info_t{ 232 | char name[4]; 233 | uint id; // Disk id code 234 | uint fs_type; 235 | uint fs_size; // MB 236 | uint disk_size; // MB 237 | } fs_info_t; 238 | 239 | 240 | // Get filesystem info 241 | // Output: info 242 | // disk_index is referred to the index of a disk on the currently 243 | // available disks list. 244 | // returns number of available disks 245 | uint get_fsinfo(uint disk_index, fs_info_t *info); 246 | 247 | 248 | // Get filesystem entry 249 | // Output: entry 250 | // parent and/or disk can be UNKNOWN_VALUE if they are unknown. 251 | // In this case they will be deducted from path 252 | // Paths must be: 253 | // - absolute or relative to system disk if parent and/or disk are unknown 254 | // - relative to parent if parent index entry or disk id are provided 255 | // Returns ERROR_NOT_FOUND if error, entry index otherwise 256 | #define UNKNOWN_VALUE 0xFFFFFFFF 257 | uint get_entry(fs_entry_t *entry, char *path, uint parent, uint disk); 258 | 259 | 260 | // Read file 261 | // Output: buff 262 | // Reads count bytes of path file starting at byte offset inside this file. 263 | // Returns number of readed bytes or ERROR_NOT_FOUND 264 | uint read_file(void *buff, char *path, uint offset, uint count); 265 | 266 | // Write file flags 267 | #define FWF_CREATE 0x0001 // Create if does not exist 268 | #define FWF_TRUNCATE 0x0002 // Truncate to last written position 269 | 270 | // Write file 271 | // Writes count bytes of path file starting at byte offset inside this file. 272 | // If target file is not big enough, its size is increased. 273 | // Depending on flags, path file can be created or truncated. 274 | // Returns number of written bytes or ERROR_NOT_FOUND 275 | uint write_file(void *buff, char *path, uint offset, uint count, uint flags); 276 | 277 | 278 | // Move entry 279 | // In the case of directories, they are recursively moved 280 | // Returns: 281 | // - ERROR_NOT_FOUND if source does not exist 282 | // - ERROR_EXISTS if destination exists 283 | // - another value otherwise 284 | uint move(char *srcpath, char *dstpath); 285 | 286 | 287 | // Copy entry 288 | // In the case of directories, they are recursively copied 289 | // Returns: 290 | // - ERROR_NOT_FOUND if source does not exist 291 | // - ERROR_EXISTS if destination exists 292 | // - another value otherwise 293 | uint copy(char *srcpath, char *dstpath); 294 | 295 | // Delete entry 296 | // In the case of directories, they are recursively deleted 297 | // Returns: 298 | // - ERROR_NOT_FOUND if path does not exist 299 | // - index of deleted entry otherwise 300 | uint delete(char *path); 301 | 302 | 303 | // Create a directory 304 | // Returns: 305 | // - ERROR_NOT_FOUND if parent path does not exist 306 | // - ERROR_EXISTS if destination already exists 307 | // - index of created entry otherwise 308 | uint create_directory(char *path); 309 | 310 | 311 | // List directory entries 312 | // Output: entry 313 | // Gets entry with index n in path directory 314 | // Returns: 315 | // - ERROR_NOT_FOUND if path does not exist 316 | // - number of elements in ths directory otherwise 317 | uint list(fs_entry_t *entry, char *path, uint n); 318 | 319 | 320 | // Create filesystem in disk 321 | // Deletes all files, creates NSFS filesystem 322 | // and adds a copy of the kernel. 323 | // Returns 0 on success 324 | uint format(uint disk); 325 | 326 | 327 | 328 | // About IP format 329 | // Unless something different is specified: 330 | // uint8_t *ip are arrays of 4 bytes with a parsed IP 331 | // char *ip are strings containing an unparsed IP 332 | #define IP_LEN 4 333 | typedef struct net_address_t { 334 | uint8_t ip[IP_LEN]; 335 | uint16_t port; 336 | } net_address_t; 337 | 338 | // Convert string to IP 339 | void str_to_ip(uint8_t *ip, const char *str); 340 | 341 | // Convert IP to string 342 | char *ip_to_str(char *str, uint8_t *ip); 343 | 344 | // Send len bytes of buff to dst through network 345 | // Uses UDP protocol. Reception not guaranteed 346 | // (uses UDP port 8086 for source) 347 | // Returns NO_ERROR on success 348 | uint send(net_address_t *dst, uint8_t *buff, size_t len); 349 | 350 | // Get and remove received data from the network reception buffer. 351 | // src and buff are filled by the function. 352 | // If more than buff_size bytes were received, 353 | // these will be lost. 354 | // Returns number of bytes of buff that have been filled 355 | // or 0 if nothing received. 356 | // When the reception buffer is full, all new received packets 357 | // are discarded. So, recv must be called frequently 358 | // Call recv_set_port once before start calling to this 359 | // function to enable a reception port. 360 | // Uses UDP protocol 361 | uint recv(net_address_t *src, uint8_t *buff, size_t buff_size); 362 | 363 | // Enable reception in this port and disable all others. 364 | // Must be called once before start calling recv. 365 | // Specify a UDP port as parameter 366 | void recv_set_port(uint16_t port); 367 | 368 | 369 | 370 | // Play sound file, non blocking 371 | // Return NO_ERROR on success, error code otherwise. 372 | // Can only play wav files 373 | uint sound_play(const char *wav_file); 374 | 375 | // Return true while a sound is playing 376 | // Return false otherwise 377 | bool sound_is_playing(); 378 | 379 | // Stop playing sound 380 | void sound_stop(); 381 | 382 | 383 | #endif // _ULIB_H 384 | -------------------------------------------------------------------------------- /source/programs/edit.c: -------------------------------------------------------------------------------- 1 | // User program: Text editor 2 | 3 | #include "types.h" 4 | #include "ulib/ulib.h" 5 | 6 | // modes for next_line function. See function 7 | #define SHOW_CURRENT 0 8 | #define SKIP_CURRENT 1 9 | 10 | 11 | // char attributes for title bar and editor area 12 | // title: black text over light gray background 13 | #define TITLE_ATTRIBUTES (AT_T_BLACK|AT_B_LGRAY) 14 | // editor: white text over blue background 15 | #define EDITOR_ATTRIBUTES (AT_T_WHITE|AT_B_BLUE) 16 | 17 | // Screen size 18 | #define SCREEN_WIDTH 80 19 | #define SCREEN_HEIGHT 28 20 | 21 | // This buffer holds a copy of screen chars 22 | // Screen is only updated when it's actually needed 23 | static char *screen_buff = NULL; 24 | 25 | // Check the local screen buffer before actually updating 26 | // the screen 27 | static void editor_putchar(uint col, uint row, char c) 28 | { 29 | const uint screen_offset = col + (row-1)*SCREEN_WIDTH; 30 | const char buff_c = *(screen_buff + screen_offset); 31 | 32 | if(c != buff_c) { 33 | *(screen_buff + screen_offset) = c; 34 | putc_attr(col, row, c, EDITOR_ATTRIBUTES); 35 | } 36 | } 37 | 38 | // Given a string (line input parameter) 39 | // returns a pointer to next line or to end of string 40 | // if there are not more lines 41 | // 42 | // Initial line of input string can be: 43 | // - shown at given row if mode==SHOW_CURRENT 44 | // - just skipped if mode==SKIP_CURRENT 45 | static char *next_line(uint mode, uint row, char *line) 46 | { 47 | // Process string until next line or end of string 48 | // and show text if needed 49 | uint col = 0; 50 | while(line && *line && *line!='\n' && col0 && *buff; offset--, buff++) { 101 | 102 | if(*buff == '\n') { 103 | (*line)++; 104 | *col = 0; 105 | } else { 106 | (*col)++; 107 | if(*col >= SCREEN_WIDTH) { 108 | (*line)++; 109 | *col = 0; 110 | } 111 | } 112 | 113 | } 114 | } 115 | 116 | // Converts line and col (input params) to 117 | // linear buffer offset (return value) 118 | uint linecol_to_buffer_offset(char *buff, uint col, uint line) 119 | { 120 | uint offset = 0; 121 | uint c = 0; 122 | while(*buff && line>0) { 123 | if(*buff == '\n' || c >= SCREEN_WIDTH) { 124 | line--; 125 | c=0; 126 | } 127 | c++; 128 | offset++; 129 | buff++; 130 | } 131 | 132 | while(*buff && col>0) { 133 | if(*buff == '\n') { 134 | break; 135 | } 136 | col--; 137 | offset++; 138 | buff++; 139 | } 140 | 141 | return offset; 142 | } 143 | 144 | // Convert linear buffer offset (uint offset) 145 | // to file line 146 | // Only function which is not screen space 147 | uint buffer_offset_to_fileline(char *buff, uint offset) 148 | { 149 | uint line = 0; 150 | for(; offset>0 && *buff; offset--, buff++) { 151 | if(*buff == '\n') { 152 | line++; 153 | } 154 | } 155 | 156 | return line; 157 | } 158 | 159 | // Program entry point 160 | int main(int argc, char *argv[]) 161 | { 162 | // Chck usage 163 | if(argc != 2) { 164 | putstr("Usage: %s \n\n", argv[0]); 165 | putstr(" can be:\n"); 166 | putstr("-an existing file path: opens existing file to edit\n"); 167 | putstr("-a new file path: opens empty editor. File is created on save\n"); 168 | putstr("\n"); 169 | return 1; 170 | } 171 | 172 | // Allocate fixed size text buffer 173 | char *buff = malloc(0xFFFF); 174 | if(!buff) { 175 | putstr("Error: can't allocate memory\n"); 176 | return 1; 177 | } 178 | 179 | // Find file 180 | fs_entry_t entry; 181 | const uint n = get_entry(&entry, argv[1], UNKNOWN_VALUE, UNKNOWN_VALUE); 182 | 183 | // buff_size is the size in bytes actually used in buff 184 | size_t buff_size = 0; 185 | 186 | // Load file or show error 187 | if(n= 0xFFFE) { 190 | mfree(buff); 191 | putstr("Can't read file %s (too big)\n", argv[1]); 192 | return 1; 193 | } 194 | 195 | memset(buff, 0, entry.size); 196 | buff_size = entry.size; 197 | uint result = read_file(buff, argv[1], 0, buff_size); 198 | debug_putstr("File read\n"); 199 | 200 | if(result >= ERROR_ANY) { 201 | mfree(buff); 202 | putstr("Can't read file %s (error=%x)\n", argv[1], result); 203 | return 1; 204 | } 205 | 206 | if(result != entry.size) { 207 | mfree(buff); 208 | putstr("Can't read file (readed %u bytes, expected %u)\n", 209 | result, entry.size); 210 | return 1; 211 | } 212 | // Buffer must finish with a 0 and 213 | // must fit at least this 0, so buff_size can't be 0 214 | if(buff_size == 0 || *(buff + buff_size-1) != 0) { 215 | buff_size++; 216 | } 217 | } else if(nKEY_F1 && k<=KEY_F10) || 285 | k==KEY_F11 || k==KEY_F12 || 286 | k==KEY_PRT_SC || k==KEY_INS || 287 | k==0) { 288 | continue; 289 | 290 | // Key F1: Save 291 | } else if(k == KEY_F1) { 292 | const uint result = write_file(buff, argv[1], 0, 293 | buff_size, FWF_CREATE|FWF_TRUNCATE); 294 | 295 | // Update state indicator 296 | if(result < ERROR_ANY) { 297 | putc_attr(strlen(argv[1]), 0, ' ', TITLE_ATTRIBUTES); 298 | } else { 299 | putc_attr(strlen(argv[1]), 0, '*', (TITLE_ATTRIBUTES&0xF0)|AT_T_RED); 300 | } 301 | // This opperation takes some time 302 | // Keyboard buffer could be cleared here 303 | 304 | // Cursor keys: Move cursor 305 | } else if(k == KEY_UP) { 306 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 307 | if(line > 0) { 308 | line -= 1; 309 | buff_cursor_offset = linecol_to_buffer_offset(buff, col, line); 310 | } 311 | 312 | } else if(k == KEY_DOWN) { 313 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 314 | line += 1; 315 | buff_cursor_offset = linecol_to_buffer_offset(buff, col, line); 316 | 317 | } else if(k == KEY_LEFT) { 318 | if(buff_cursor_offset > 0) { 319 | buff_cursor_offset--; 320 | } 321 | 322 | } else if(k == KEY_RIGHT) { 323 | if(buff_cursor_offset < buff_size - 1) { 324 | buff_cursor_offset++; 325 | } 326 | 327 | // HOME, END, PG_UP and PG_DN keys 328 | } else if(k == KEY_HOME) { 329 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 330 | col = 0; 331 | buff_cursor_offset = linecol_to_buffer_offset(buff, col, line); 332 | 333 | } else if(k == KEY_END) { 334 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 335 | col = 0xFFFF; 336 | buff_cursor_offset = linecol_to_buffer_offset(buff, col, line); 337 | 338 | } else if(k == KEY_PG_DN) { 339 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 340 | line += SCREEN_HEIGHT-1; 341 | buff_cursor_offset = linecol_to_buffer_offset(buff, col, line); 342 | 343 | } else if(k == KEY_PG_UP) { 344 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 345 | line -= min(line, SCREEN_HEIGHT-1); 346 | buff_cursor_offset = linecol_to_buffer_offset(buff, col, line); 347 | 348 | 349 | // Backspace key: delete char before cursor and move cursor there 350 | } else if(k == KEY_BACKSPACE) { 351 | if(buff_cursor_offset > 0) { 352 | memcpy(buff+buff_cursor_offset-1, buff+buff_cursor_offset, 353 | buff_size-buff_cursor_offset); 354 | buff_size--; 355 | putc_attr(strlen(argv[1]), 0, '*', TITLE_ATTRIBUTES); 356 | buff_cursor_offset--; 357 | } 358 | 359 | // Del key: delete char at cursor 360 | } else if(k == KEY_DEL) { 361 | if(buff_cursor_offset < buff_size-1) { 362 | memcpy(buff+buff_cursor_offset, buff+buff_cursor_offset+1, 363 | buff_size-buff_cursor_offset-1); 364 | buff_size--; 365 | putc_attr(strlen(argv[1]), 0, '*', TITLE_ATTRIBUTES); 366 | } 367 | 368 | // Any other key but esc: insert char at cursor 369 | } else if(k != KEY_ESC && k != 0) { 370 | 371 | if(k == KEY_RETURN) { 372 | k = '\n'; 373 | } 374 | if(k == KEY_TAB) { 375 | k = '\t'; 376 | } 377 | memcpy(buff+buff_cursor_offset+1, buff+buff_cursor_offset, 378 | buff_size-buff_cursor_offset); 379 | *(buff + buff_cursor_offset++) = k; 380 | buff_size++; 381 | putc_attr(strlen(argv[1]), 0, '*', TITLE_ATTRIBUTES); 382 | } 383 | 384 | // Update cursor position and display 385 | buffer_offset_to_linecol(buff, buff_cursor_offset, &col, &line); 386 | if(line < current_line) { 387 | current_line = line; 388 | } else if(line > current_line + SCREEN_HEIGHT - 2) { 389 | current_line = line - SCREEN_HEIGHT + 2; 390 | } 391 | 392 | // Update line number in title 393 | // Compute bcd value (reversed) 394 | char line_ibcd[4]={0}; // To store line number digits 395 | uint ibcdt = 396 | min(9999, buffer_offset_to_fileline(buff, buff_cursor_offset)+1); 397 | for(uint i=0; i<4; i++) { 398 | line_ibcd[i] = ibcdt%10; 399 | ibcdt /= 10; 400 | if(ibcdt==0) { 401 | ibcdt = i; 402 | break; 403 | } 404 | } 405 | // Display it 406 | const uint start_pos = SCREEN_WIDTH-strlen(title_info)+2; 407 | for(uint i=0; i<4; i++) { 408 | char c = i<=ibcdt?line_ibcd[ibcdt-i]+'0':' '; 409 | putc_attr(start_pos+i, 0, c, TITLE_ATTRIBUTES); 410 | } 411 | 412 | set_show_cursor(0); 413 | show_buffer_at_line(buff, current_line); 414 | line -= current_line; 415 | line += 1; 416 | set_cursor_pos(col, line); 417 | set_show_cursor(1); 418 | } 419 | 420 | // Free screen buffer 421 | mfree(screen_buff); 422 | 423 | // Free buffer 424 | mfree(buff); 425 | 426 | // Reset screen 427 | clear_screen(); 428 | set_cursor_pos(0, 0); 429 | 430 | return 0; 431 | } 432 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NANO S32 2 | 3 | NANO S32 is a small operating system for 32-bit x86 architectures which operates in protected mode. 4 | 5 | System requirements: 6 | * x86 compatible computer 7 | * 1MB of RAM 8 | * 1.44Mb disk 9 | * VGA graphics card 10 | 11 | Realtek 8029AS network card is supported. 12 | Sound Blaster 16 sound card is supported. 13 | 14 | Developer notes: 15 | This software is a hobby operating system. Creator makes no warranty for its use and assumes no responsibility for any errors which may appear in this document. 16 | 17 | ## System description 18 | This operating system is composed of several components, detailed in this section. 19 | 20 | ### The kernel 21 | The kernel is the core of the operating system. It connects the application software to the hardware of a computer. The kernel manages memory access for user programs, determines how programs get access to hardware resources, and organizes the data for long-term non-volatile storage with file systems on disks. This operating system kernel is written in C and assembler. 22 | 23 | #### Program execution 24 | The operating system provides an interface between an application program and the computer hardware, so that an application program can interact with the hardware through procedures programmed into the operating system. To do so, the operating system provides a set of services which simplify development and execution of application programs. 25 | 26 | To execute an application program the operating system assigns memory space and other resources, loads program binary code into memory, and initiates execution of the application program which then interacts with the user and with hardware devices. 27 | 28 | This operating system implements a monotasking task model: only one thread of execution is run at a given time. When an application is executed, it takes control of the whole computer, save for the 'resident' part of the operating system which handles system calls and hardware interrupts. 29 | 30 | #### Operation Mode 31 | This operating system operates kernel and applications in Protected Mode, although some of its endowed hardware controllers perform temporally switches to 16-bit Real Mode to access BIOS services. 32 | 33 | #### Memory management 34 | The operating system kernel is responsible for managing the system memory. This operating system enables A20 line (if available) at startup and uses a flat memory model. 4GB of memory are addressable. A heap is managed by the kernel to allow user memory allocation. 35 | 36 | #### Disk access and file systems 37 | The specific way in which files are stored on a disk is called a file system. File systems allow users and programs to organize files on a computer. 38 | 39 | This operating system supports hard disks and removable disks, and implements a custom file system (NSFS). The NSFS file system is simple and easy to implement. 40 | 41 | NSFS divides disk space into logical blocks of contiguous space, following this layout: 42 | 43 | [boot block | super block | entries table | data blocks] 44 | * Boot block (block 0): Boot sector 45 | * Super block (block 1): Contains information about the layout of the file system 46 | * Entries table (blocks 2-n): Table of file and directory entries 47 | * Data blocks (blocks n-end): Data blocks referenced by file entries 48 | 49 | ### User Interface 50 | This operating system implements a command-line interface (CLI), where computer commands are typed out line-by-line. The User Manual section contains a more detailed description. 51 | 52 | ### Boot process 53 | When a computer is switched on or reset, it runs through a series of diagnostics called POST (Power-On Self-Test). This sequence culminates in locating a bootable device, such as a removable disk or a hard disk in the order that the firmware is configured to. A device is detected as bootable if it carries a boot sector (512 bytes) with the byte sequence 0x55, 0xAA in bytes 511 and 512 respectively. When the BIOS finds such a boot sector, it loads it into memory at 0x0000:0x7C00 (segment 0, offset 0x7C00). Execution is then transferred to this address. 54 | 55 | The NANO system disk contains a bootloader in this sector. This bootloader scans then the boot disk and loads the kernel file at memory location 0x8000. Once the bootloader has loaded the kernel, it jumps to this memory location to begin executing it. 56 | 57 | The kernel execution starts, sets up needed drivers and initializes the Command Line Interface. 58 | 59 | ### Executable format 60 | NANO executable file format does not contain metadata or sections. It only contains non-relocatable raw x86 machine code assumed to be loaded at 0x20000 address. Execution starts at this address through a call instruction. The CLI requires a file name to contain the `.bin` suffix before starting the execution of such file. 61 | 62 | ## Building 63 | The building process is expected to be executed in a Linux system. In Windows 10 it can be built using Windows Subsystem for Linux. 64 | 65 | 1. Install required software: 66 | * make, gcc, nasm to build the operating system and user programs 67 | * optionally install qemu x86 emulator to test the images in a virtual machine 68 | * optionally use dd to write disk images on storage devices 69 | ``` 70 | sudo apt-get install gcc nasm make 71 | sudo apt-get install qemu qemu-system-x86 72 | ``` 73 | 74 | 2. Get full source code tree. 75 | ``` 76 | (sudo apt-get install git) 77 | git clone https://github.com/NANO-DEV/NANO-S32.git 78 | ``` 79 | The tree contains the following directories: 80 | * fstools: disk image generation tool 81 | * images: output folder for generated disk images 82 | * source: source code 83 | * boot: code for the boot sector image 84 | * ulib: library to develop user programs 85 | * programs: user programs 86 | 87 | 3. Build: Run `make` from the root directory to build everything. Images will be generated in the `images` directory. Optionally, `Makefile` and `source/Makefile` files can be customized. 88 | 89 | ## Testing 90 | After building, run `make qemu` (linux) or `qemu.bat` (windows) from the root directory to test the operating system in qemu. Other virtual machines have been successfully tested. To test the system using VirtualBox, create a new `Other/DOS` machine and start it with `images/os_fd.img` as floppy image. The Sound Blaster driver seems not to work in VirtualBox. 91 | 92 | The network support has been only tested in qemu. In Windows it's possible to have internet access using the Tap-windows driver provided [here](https://openvpn.net/index.php/download/community-downloads.html). This virtual device must be renamed to `tap` and bridged to the actual nic in order to make the default `qemu.bat` script work as expected. 93 | 94 | The operating system outputs debug information through the first serial port in real time. This can be useful for developers. This serial port is configured to work at 115200 bauds, 8 data bits, odd parity and 1 stop bit. 95 | 96 | Using the provided qemu scripts, the serial port is automatically mapped to the process standard input/output. For VirtualBox users, it is possible for example to telnet from putty if COM1 is set to TCP mode without a pipe, and the same port is specified in both programs or to dump the serial port output to a text file. 97 | 98 | The system can operate real hardware if images are written to physical disks. Writing images to disks to test them in real hardware is dangerous and can cause loss of data or boot damage, among other undesired things, in all involved computers. So, it is not recommended to proceed unless this risk is understood and assumed. To write images to physical disks, `dd` can be used in linux: 99 | ``` 100 | dd if=images/os-fd.img of=/dev/sdb status=none 101 | ``` 102 | Replace `/dev/sdb` with the actual target storage device. Usually `root` privileges will be needed to run this command. 103 | 104 | ## User manual 105 | ### First boot 106 | A system disk is needed to start the operating system for first time. This disk contains special files required for the computer to boot. If the system disk is removable media like a floppy diskette, it must be introduced in the computer before the boot sequence starts (for example, before turning the computer on). Check also the settings in the BIOS to ensure proper boot device configuration. 107 | 108 | See later sections for information about how to install (clone system) once it is running. 109 | 110 | ### Command Line Interface 111 | Once the computer is turned on, and after boot sequence, the operating system automatically starts the Command Line Interface (CLI). The prompt shows an open angle bracket `>` symbol and a blinking cursor (underscore) where a command is expected to be introduced using the keyboard. 112 | 113 | A command is a short text string constituted of several parts. The first part, is the command name (just a word), which allows the system to identify which command must be executed. Sometimes, after the name, one or more parameters must be provided, depending on the specific syntax of each command. For example, `copy doc.txt doc-new.txt` is a command made of three parts: the name (`copy`) and two parameters (`doc.txt` and `doc-new.txt`), whose meaning is specific to the copy command syntax. System commands syntax is discussed in a later section. 114 | 115 | There are two different types of valid commands: 116 | * The path of an executable file (`.bin`) will cause the system to execute it. 117 | * There are also some built-in commands in the CLI (see later section). 118 | 119 | The `.bin` suffix can be omitted for executable files. For example, to run `edit.bin`, it's enough to write `edit`. After typing a command, `ENTER` must be pressed in order to execute it. 120 | 121 | ### File system and paths 122 | Information is stored on disks using a files and directories analogy, called file system. Directories perform a classifying function, and can contain other directories and files. Files are the actual data holders, and can only contain information, such as text, images or even executable code. A disk contains at least a top level directory, usually referred to as root directory. This term comes from an analogy where a file system is a tree, the directories are its branches and the files are its leaves. 123 | 124 | Since most commands perform files or directories operations, a way to identify which files or directories to operate is needed. This is achieved through a specific type of structured text strings that unequivocally refer a file or directory, named paths. Paths are usually used as command parameters. 125 | 126 | Paths can be specified as absolute paths or relative to the system disk. When specified as absolute, they must begin with a disk identifier. Possible disk identifiers are: 127 | * fd0 - First floppy disk 128 | * fd1 - Second floppy disk 129 | * hd0 - First hard disk 130 | * hd1 - Second hard disk 131 | 132 | After the optional disk identifier, paths are formed of a sequence of components. Each component, represents a branch in the tree (a directory name), describing the full path from the root to a given branch or leave. Path components are separated with slashes `/`. The root directory of a disk can be omitted or referred as `.`. 133 | 134 | Examples of valid paths: 135 | ``` 136 | fd0 137 | hd0/. 138 | hd0/documents/file.txt 139 | docs/other/readme.txt 140 | ``` 141 | 142 | Note that all files and directories have their own name. This name must not contain spaces or slashes, and must be unique (differentiation is case sensitive) inside its parent directory. 143 | 144 | When booting the operating system from a flash drive, the BIOS emulates instead a floppy disk or a hard disk, so the flash drive can still be accessed using one of the previous identifiers. 145 | 146 | ### CLI Built-in commands 147 | 148 | The CLI provides some built-in commands, detailed in this section. When executed with wrong syntax, a syntax reminder is printed to screen. 149 | 150 | #### CLONE 151 | Clone the system disk in another disk. The target disk, after being formatted, will be able to boot and will contain a copy of all the files in the current system disk. Any previously existing data in the target disk will be lost. One parameter is expected: the target disk identifier. 152 | 153 | Example: 154 | ``` 155 | clone hd0 156 | ``` 157 | 158 | #### CLS 159 | Clear the screen. 160 | 161 | #### CONFIG 162 | Show, set or save configuration parameter values. To show current values, call it without arguments. To set values, two parameters are expected: the parameter name to set, and its value. To save configuration to file, use `save` parameter. Saved configuration will be automatically read when the system starts. 163 | 164 | Example: 165 | ``` 166 | config 167 | config save 168 | config net_IP 192.168.0.20 169 | config net_gate 192.168.0.1 170 | ``` 171 | 172 | #### COPY 173 | Copy files. Two parameters are expected: the path of the file to copy, and the path of the new copy. 174 | 175 | Example: 176 | ``` 177 | copy doc.txt doc-copy.txt 178 | ``` 179 | 180 | #### DELETE 181 | Delete a file or a directory. One parameter is expected: the path of the file or directory to delete. 182 | 183 | Example: 184 | ``` 185 | delete doc.txt 186 | ``` 187 | 188 | #### HELP 189 | Show basic help. 190 | 191 | #### INFO 192 | Show system version and hardware information. 193 | 194 | #### LIST 195 | List the contents of a directory. One parameter is expected: the path of the directory to list. If this parameter is omitted, the contents of the system disk root directory will be listed. 196 | 197 | Example: 198 | ``` 199 | list fd0/documents 200 | ``` 201 | 202 | #### MAKEDIR 203 | Create a new directory. One parameter is expected: the path of the new directory. 204 | 205 | Example: 206 | ``` 207 | makedir documents/newdir 208 | ``` 209 | 210 | #### MOVE 211 | Move files. Two parameters are expected: the current path of the file to move, and the new path. 212 | 213 | Example: 214 | ``` 215 | move fd0/doc.txt hd0/documents/doc.txt 216 | ``` 217 | 218 | #### READ 219 | Display the contents of a file. The path of the file to display is expected as only parameter. Optionally, if `hex` is passed as first parameter, contents are dumped in hexadecimal instead of ASCII. 220 | 221 | Example: 222 | ``` 223 | read documents/doc.txt 224 | read hex sample.bin 225 | ``` 226 | 227 | #### SHUTDOWN 228 | Shutdowns the computer or halts it if APM is not supported. 229 | 230 | #### TIME 231 | Show current date and time. 232 | 233 | ## User programs development 234 | 235 | The easiest way develop new user programs is thorugh corss development: 236 | 237 | 1. Setup the development system so it contains a copy of the full operating system source tree and it's able to build and test it. See the building and testing sections in this document. 238 | 239 | 2. Create a new `programname.c` file in `source/programs` folder with this code: 240 | ``` 241 | // User program: programname 242 | 243 | #include "types.h" 244 | #include "ulib/ulib.h" 245 | 246 | int main(int argc, char *argv[]) 247 | { 248 | return 0; 249 | } 250 | ``` 251 | 3. Edit this line of `source/Makefile` and add `$(PROGDIR)programname.bin` at the end, like this: 252 | 253 | ``` 254 | programs: $(PROGDIR)edit.bin $(PROGDIR)programname.bin 255 | ``` 256 | 4. Edit this line of `Makefile` and add `$(SOURCEDIR)programs/programname.bin` at the end, like this: 257 | 258 | ``` 259 | USERFILES := $(SOURCEDIR)programs/edit.bin $(SOURCEDIR)programs/programname.bin 260 | ``` 261 | 5. Add code to `programname.c`. See all available system functions in `ulib/ulib.h` 262 | 6. Build and test. 263 | -------------------------------------------------------------------------------- /source/ulib/ulib.c: -------------------------------------------------------------------------------- 1 | // User library 2 | 3 | #include "types.h" 4 | #include "ulib.h" 5 | #include "syscall.h" 6 | 7 | // System call 8 | uint syscall(uint service, void *param) 9 | { 10 | uint __res = 0; 11 | __asm__ volatile( "int $49;" 12 | : "=a" ((uint)(__res)) 13 | : "0" (service), "b" ((uint)(service)),"c" ((void*)(param)) 14 | : "memory" 15 | ); 16 | return __res; 17 | } 18 | 19 | // x86 specific helper function 20 | static inline void stosb(void *addr, int data, size_t cnt) 21 | { 22 | __asm__ volatile("cld; rep stosb" : 23 | "=D" (addr), "=c" (cnt) : 24 | "0" (addr), "1" (cnt), "a" (data) : 25 | "memory", "cc"); 26 | } 27 | 28 | // Compare strings 29 | size_t strcmp(const char *str1, const char *str2) 30 | { 31 | size_t i = 0; 32 | while(str1[i]==str2[i] && str1[i]!=0) { 33 | i++; 34 | } 35 | return str1[i] - str2[i]; 36 | } 37 | 38 | // Get string length 39 | size_t strlen(const char *str) 40 | { 41 | size_t len = 0; 42 | while(str[len]) { 43 | len++; 44 | } 45 | return len; 46 | } 47 | 48 | // Copy string src to dst without exceeding 49 | // n elements in dst 50 | size_t strncpy(char *dst, const char *src, size_t n) 51 | { 52 | size_t i = 0; 53 | while(src[i]!=0 && i+1='a'? 0xA+*src-'a' : 0xA+*src-'A'; 127 | 128 | value = value*base + digit; 129 | src++; 130 | } 131 | return value; 132 | } 133 | 134 | // Set n bytes from dst to c 135 | void *memset(void *dst, int c, size_t n) 136 | { 137 | stosb(dst, c, n); 138 | return dst; 139 | } 140 | 141 | // Copy n bytes from src to dst 142 | size_t memcpy(void *dst, const void *src, uint n) 143 | { 144 | uint8_t *vdst = dst; 145 | const uint8_t *vsrc = src; 146 | const uint rdir = (src>dst)?0:1; 147 | 148 | size_t i = 0; 149 | for(i=0; i='0' && *format<='9') { 208 | n_digits=n_digits*10 + *format-'0'; 209 | format++; 210 | } 211 | 212 | if(*format == 'd') { 213 | base = 10; 214 | if(is_negative) { 215 | value = -(int)value; 216 | } 217 | } else if(*format == 'u') { 218 | base = 10; 219 | } else if(*format == 'x') { 220 | base = 16; 221 | n_digits = n_digits?n_digits:8; 222 | } else if(*format == 's') { 223 | while(*(char*)value) { 224 | putchar(*(char*)(value++)); 225 | char_count++; 226 | } 227 | format++; 228 | } else if(*format == 'c') { 229 | if(n_digits) { 230 | while(char_count 0) { 408 | memcpy(&str[i-1], &str[i], n-i); 409 | i--; 410 | } 411 | 412 | // Delete 413 | } else if(k == KEY_DEL) { 414 | if(i < n-1) { 415 | memcpy(&str[i], &str[i+1], n-i-1); 416 | } 417 | 418 | // Return 419 | } else if(k == KEY_RETURN) { 420 | break; 421 | 422 | // Cursor movement keys 423 | } else if(k == KEY_LEFT && i>0) { 424 | i--; 425 | } else if(k == KEY_RIGHT && i0x7E)) { 434 | continue; 435 | 436 | // Append char to string 437 | } else if(strlen(str) < n-1 && (k & 0xFF)) { 438 | memcpy(&str[i+1], &str[i], n-i-2); 439 | str[i++] = (k & 0xFF); 440 | } 441 | 442 | // Hide cursor and redraw string at initial position 443 | set_show_cursor(0); 444 | set_cursor_pos(col, row); 445 | for(uint p=0; p= ERROR_ANY) { 62 | putstr("path not found\n"); 63 | return; 64 | } 65 | if(n > 0) { 66 | putstr("\n"); 67 | // Print one by one 68 | for(uint i=0; i= ERROR_ANY) { 72 | putstr("Error\n"); 73 | break; 74 | } 75 | // Listed entry is a dir? If so, 76 | // start this line with a '+' 77 | char line[64] = {0}; 78 | memset(line, 0, sizeof(line)); 79 | strncpy(line, entry.flags & T_DIR ? "+ " : " ", sizeof(line)); 80 | strncat(line, (char*)entry.name, sizeof(line)); // Append name 81 | // We want size to be right-aligned so add spaces 82 | // depending on figures of entry size 83 | uint c = 0; 84 | for(c=strlen(line); c<22; c++) { 85 | line[c] = ' '; 86 | } 87 | uint size = entry.size; 88 | while((size = size / 10)) { 89 | line[--c] = 0; 90 | } 91 | // Print name and size 92 | putstr("%s%u %s ", line, (uint)entry.size, 93 | (entry.flags & T_DIR) ? "items" : "bytes"); 94 | // Print date 95 | time_t etime; 96 | fs_fstime_to_systime(entry.time, &etime); 97 | putstr("%4u/%2u/%2u %2u:%2u:%2u\n", 98 | etime.year, 99 | etime.month, 100 | etime.day, 101 | etime.hour, 102 | etime.minute, 103 | etime.second); 104 | } 105 | putstr("\n"); 106 | } 107 | } else { 108 | putstr("usage: list \n"); 109 | } 110 | } 111 | 112 | // Create directory 113 | static void cli_makedir(uint argc, char *argv[]) 114 | { 115 | if(argc == 2) { 116 | const uint result = fs_create_directory(argv[1]); 117 | if(result == ERROR_NOT_FOUND) { 118 | putstr("error: path not found\n"); 119 | } else if(result == ERROR_EXISTS) { 120 | putstr("error: destination already exists\n"); 121 | } else if(result == ERROR_NO_SPACE) { 122 | putstr("error: can't allocate destination in filesystem\n"); 123 | } else if(result >= ERROR_ANY) { 124 | putstr("error: coludn't create directory\n"); 125 | } 126 | } else { 127 | putstr("usage: makedir \n"); 128 | } 129 | } 130 | 131 | static void cli_delete(uint argc, char *argv[]) 132 | { 133 | if(argc == 2) { 134 | const uint result = fs_delete(argv[1]); 135 | if(result >= ERROR_ANY) { 136 | putstr("error: failed to delete\n"); 137 | } 138 | } else { 139 | putstr("usage: delete \n"); 140 | } 141 | } 142 | 143 | // Move/rename 144 | static void cli_move(uint argc, char *argv[]) 145 | { 146 | if(argc == 3) { 147 | const uint result = fs_move(argv[1], argv[2]); 148 | if(result == ERROR_NOT_FOUND) { 149 | putstr("error: path not found\n"); 150 | } else if(result == ERROR_EXISTS) { 151 | putstr("error: destination already exists\n"); 152 | } else if(result == ERROR_NO_SPACE) { 153 | putstr("error: can't allocate destination in filesystem\n"); 154 | } else if(result >= ERROR_ANY) { 155 | putstr("error: coludn't move files\n"); 156 | } 157 | } else { 158 | putstr("usage: move \n"); 159 | } 160 | } 161 | 162 | static void cli_copy(uint argc, char *argv[]) 163 | { 164 | if(argc == 3) { 165 | const uint result = fs_copy(argv[1], argv[2]); 166 | if(result == ERROR_NOT_FOUND) { 167 | putstr("error: path not found\n"); 168 | } else if(result == ERROR_EXISTS) { 169 | putstr("error: destination already exists\n"); 170 | } else if(result == ERROR_NO_SPACE) { 171 | putstr("error: can't allocate destination in filesystem\n"); 172 | } else if(result >= ERROR_ANY) { 173 | putstr("error: coludn't copy files\n"); 174 | } 175 | } else { 176 | putstr("usage: copy \n"); 177 | } 178 | } 179 | 180 | // Show system info 181 | static void cli_info(uint argc) 182 | { 183 | if(argc == 1) { 184 | putstr("\n"); 185 | putstr("NANO-S32 [Version %u.%u build %u]\n", 186 | OS_VERSION_HI, OS_VERSION_LO, OS_BUILD_NUM); 187 | putstr("\n"); 188 | putstr("Disks:\n"); 189 | fs_init_info(); // Rescan disks 190 | for(uint i=0; i= ERROR_ANY) { 267 | putstr("Error creating file list\n"); 268 | return; 269 | } 270 | // List entries 271 | for(uint i=0; i= ERROR_ANY) { 275 | putstr("Error copying files. Aborted\n"); 276 | break; 277 | } 278 | strncpy(dst, argv[1], sizeof(dst)); 279 | strncat(dst, PATH_SEPARATOR_S, sizeof(dst)); 280 | strncat(dst, (char*)entry.name, sizeof(dst)); 281 | putstr("Copying %s to %s...\n", entry.name, dst); 282 | debug_putstr("copy %s %s\n", entry.name, dst); 283 | result = fs_copy((char*)entry.name, dst); 284 | fs_print_map(dst); 285 | // Skip ERROR_EXISTS errors, because system files were copied 286 | // by fs_format function, so they are expected to fail 287 | if(result >= ERROR_ANY && result != ERROR_EXISTS) { 288 | putstr("Error copying %s. Aborted\n", entry.name); 289 | break; 290 | } 291 | } 292 | // Notify result 293 | if(result < ERROR_ANY) { 294 | putstr("Operation completed\n"); 295 | } 296 | } else { 297 | putstr("usage: clone \n"); 298 | } 299 | } 300 | 301 | // Read (display) a file 302 | static void cli_read(uint argc, char *argv[]) 303 | { 304 | if(argc==2 || (argc==3 && strcmp(argv[1],"hex")==0)) { 305 | uint result = 0; 306 | uint offset = 0; 307 | char buff[512] = {0}; 308 | memset(buff, 0, sizeof(buff)); 309 | // While it can read the file, print it 310 | while((result = fs_read_file(buff, argv[argc-1], offset, sizeof(buff)))) { 311 | if(result == ERROR_NOT_FOUND) { 312 | putstr("\nInvalid input file\n"); 313 | break; 314 | } else if(result >= ERROR_ANY) { 315 | putstr("\nThere was an error reading input file\n"); 316 | break; 317 | } 318 | for(uint i=0; i\n"); 340 | } 341 | } 342 | 343 | // Show date and time 344 | static void cli_time(uint argc) 345 | { 346 | if(argc == 1) { 347 | time_t ctime = {0}; 348 | get_datetime(&ctime); 349 | putstr("\n%4u/%2u/%2u %2u:%2u:%2u\n\n", 350 | ctime.year, 351 | ctime.month, 352 | ctime.day, 353 | ctime.hour, 354 | ctime.minute, 355 | ctime.second); 356 | } else { 357 | putstr("usage: time\n"); 358 | } 359 | } 360 | 361 | // Set system config 362 | static void cli_config(uint argc, char *argv[]) 363 | { 364 | // Config command: Show or edit config parameters 365 | if(argc == 1) { 366 | putstr("\n"); 367 | putstr("net_IP: %u.%u.%u.%u\n", local_ip[0], local_ip[1], local_ip[2], local_ip[3]); 368 | putstr("net_gate: %u.%u.%u.%u\n", local_gate[0], local_gate[1], local_gate[2], local_gate[3]); 369 | putstr("\n"); 370 | } else if(argc == 2 && strcmp(argv[1], "save") == 0) { 371 | char config_str[512] = {0}; 372 | char ip_str[32] = {0}; 373 | 374 | // Save config file 375 | memset(config_str, 0, sizeof(config_str)); 376 | 377 | strncat(config_str, "config net_IP ", sizeof(config_str)); 378 | strncat(config_str, ip_to_str(ip_str, local_ip), sizeof(config_str)); 379 | strncat(config_str, "\n", sizeof(config_str)); 380 | 381 | strncat(config_str, "config net_gate ", sizeof(config_str)); 382 | strncat(config_str, ip_to_str(ip_str, local_gate), sizeof(config_str)); 383 | strncat(config_str, "\n", sizeof(config_str)); 384 | 385 | fs_write_file(config_str, "config.ini", 0, strlen(config_str)+1, WF_CREATE|WF_TRUNCATE); 386 | debug_putstr("Config file saved\n"); 387 | 388 | } else if(argc == 3) { 389 | if(strcmp(argv[1], "net_IP") == 0) { 390 | str_to_ip(local_ip, argv[2]); 391 | } else if(strcmp(argv[1], "net_gate") == 0) { 392 | str_to_ip(local_gate, argv[2]); 393 | } 394 | 395 | } else { 396 | putstr("usage:\nconfig\nconfig save\nconfig \n"); 397 | } 398 | } 399 | 400 | // Not a built-in command case: 401 | // -Try to find and run executable file 402 | // -Show "Unknown command" error if not found 403 | static void cli_extern(uint argc, char *argv[]) 404 | { 405 | // Try to find an executable file 406 | char prog_file_name[32] = {0}; 407 | strncpy(prog_file_name, argv[0], sizeof(prog_file_name)); 408 | 409 | // Append .bin if there is not a '.' in the name 410 | const char *prog_ext = ".bin"; 411 | if(!strchr(prog_file_name, '.')) { 412 | strncat(prog_file_name, prog_ext, sizeof(prog_file_name)); 413 | } 414 | // Find .bin file 415 | sfs_entry_t entry; 416 | uint result = fs_get_entry(&entry, prog_file_name, UNKNOWN_VALUE, UNKNOWN_VALUE); 417 | if(result < ERROR_ANY) { 418 | // Found 419 | if(entry.flags & T_FILE) { 420 | // It's a file: load it 421 | if(UPROG_MEMMAX < entry.size) { 422 | putstr("not enough memory\n"); 423 | return; 424 | } 425 | const uint r = fs_read_file((void*)UPROG_MEMLOC, prog_file_name, 0, entry.size); 426 | if(r>=ERROR_ANY) { 427 | putstr("error loading file\n"); 428 | debug_putstr("error loading file\n"); 429 | result = ERROR_IO; 430 | return; 431 | } 432 | } else { 433 | // It's not a file: error 434 | result = ERROR_NOT_FOUND; 435 | } 436 | } 437 | 438 | if(result >= ERROR_ANY || result == 0) { 439 | putstr("unknown command\n"); 440 | } else { 441 | // Check name ends with ".bin" 442 | if(strcmp(&prog_file_name[strchr(prog_file_name, '.') - 1], prog_ext)) { 443 | putstr("error: only %s files can be executed\n", prog_ext); 444 | return; 445 | } 446 | 447 | uint c = 0; 448 | char **arg_var = (char**)UPROG_ARGLOC; 449 | char *arg_str = (char*)UPROG_STRLOC; 450 | 451 | // Create argv copy in program segment 452 | for(uint uarg=0; uarg %s\n", str); 482 | 483 | // Tokenize 484 | uint argc = 0; 485 | char *tok = str; 486 | char *nexttok = tok; 487 | while(*tok && *nexttok && argc < CLI_MAX_ARG) { 488 | tok = strtok(tok, &nexttok, ' '); 489 | if(*tok) { 490 | argv[argc++] = tok; 491 | } 492 | tok = nexttok; 493 | } 494 | 495 | // Process command 496 | if(argc == 0) { 497 | // Empty command line, skip 498 | } 499 | // Built-in commands 500 | else if(strcmp(argv[0], "cls") == 0) { 501 | // cls command: clear the screen 502 | cli_cls(argc); 503 | 504 | } else if(strcmp(argv[0], "shutdown") == 0) { 505 | cli_shutdown(argc); 506 | 507 | } else if(strcmp(argv[0], "list") == 0) { 508 | // List files and dirs 509 | cli_list(argc, argv); 510 | 511 | } else if(strcmp(argv[0], "makedir") == 0) { 512 | cli_makedir(argc, argv); 513 | 514 | } else if(strcmp(argv[0], "delete") == 0) { 515 | cli_delete(argc, argv); 516 | 517 | } else if(strcmp(argv[0], "move") == 0) { 518 | // Move/rename files and dirs 519 | cli_move(argc, argv); 520 | 521 | } else if(strcmp(argv[0], "copy") == 0) { 522 | cli_copy(argc, argv); 523 | 524 | } else if(strcmp(argv[0], "info") == 0) { 525 | // Show system info 526 | cli_info(argc); 527 | 528 | } else if(strcmp(argv[0], "clone") == 0) { 529 | // Clone running system in a disk 530 | cli_clone(argc, argv); 531 | 532 | } else if(strcmp(argv[0], "read") == 0) { 533 | // Read (display) file contents 534 | cli_read(argc, argv); 535 | 536 | } else if(strcmp(argv[0], "time") == 0) { 537 | // Show time and date 538 | cli_time(argc); 539 | 540 | } else if(strcmp(argv[0], "config") == 0) { 541 | // Manage system config 542 | cli_config(argc, argv); 543 | 544 | } else if(strcmp(argv[0], "help") == 0) { 545 | // Show help 546 | if(argc == 1) { 547 | putstr("\n"); 548 | putstr("Built-in commands:\n"); 549 | putstr("\n"); 550 | putstr("clone - clone system in another disk\n"); 551 | putstr("cls - clear the screen\n"); 552 | putstr("config - show or set config\n"); 553 | putstr("copy - create a copy of a file or directory\n"); 554 | putstr("delete - delete entry\n"); 555 | putstr("help - show this help\n"); 556 | putstr("info - show system info\n"); 557 | putstr("list - list directory contents\n"); 558 | putstr("makedir - create directory\n"); 559 | putstr("move - move file or directory\n"); 560 | putstr("read - show file contents in screen\n"); 561 | putstr("shutdown - shutdown the computer\n"); 562 | putstr("time - show time and date\n"); 563 | putstr("\n"); 564 | } else if(argc == 2 && strcmp(argv[1], "huri") == 0) { 565 | // Easter egg 566 | putstr("\n"); 567 | putstr(" _,-/\\^---, \n"); 568 | putstr(" ;\"~~~~~~~~\"; _/;; ~~ {0 `---v \n"); 569 | putstr(" ;\" ::::: :: \"\\_ _/ ;; ~ _../ \n"); 570 | putstr(" ;\" ;; ;;; \\___/:: ;;,'~~~~ \n"); 571 | putstr(" ;\" ;;;;. ;; ;;; :: ,/ \n"); 572 | putstr(" / ;; ;;;______;;;; ;;; ::,/ \n"); 573 | putstr(" /;;V_;; _-~~~~~~~~~~;_ ;;; ,/ \n"); 574 | putstr(" | :/ / ,/ \\_ ~~)/ \n"); 575 | putstr(" |:| / /~~~= \\;; \\~~= \n"); 576 | putstr(" ;:;{::~~~~~~= \\__~~~= \n"); 577 | putstr(" ;~~:; ~~~~~~~~~ ~~~~~~ \n"); 578 | putstr(" \\/~~ \n"); 579 | putstr("\n"); 580 | } else { 581 | putstr("usage: help\n"); 582 | } 583 | 584 | } else { 585 | // Not a built-in command 586 | // Try to find and run executable file 587 | // Show "Unknown command" error if not found 588 | cli_extern(argc, argv); 589 | } 590 | } 591 | 592 | // Execute script file 593 | void cli_exec_file(char *path) 594 | { 595 | char line[72] = {0}; 596 | uint offset = 0; 597 | 598 | while(1) { 599 | // Read file 600 | const uint readed = fs_read_file(line, path, offset, sizeof(line)); 601 | 602 | if(readed==0 || readed>=ERROR_ANY) { 603 | debug_putstr("CLI: Read file (%s) error %x\n", 604 | path, readed); 605 | return; 606 | } 607 | 608 | // Isolate a line 609 | uint i = 0; 610 | for(i=0; i "); 637 | getstr(str, sizeof(str)); 638 | 639 | // Execute 640 | execute(str); 641 | } 642 | } 643 | -------------------------------------------------------------------------------- /source/sound.c: -------------------------------------------------------------------------------- 1 | // Sound functionality 2 | // Sound Blaster 16 driver 3 | 4 | #include "types.h" 5 | #include "x86.h" 6 | #include "hwio.h" 7 | #include "ulib/ulib.h" 8 | #include "fs.h" 9 | #include "sound.h" 10 | 11 | // WAV fileformat 12 | #define WAV_RIFF 0x46464952 13 | #define WAV_WAVE 0x45564157 14 | typedef struct RIFF_chunk_t { 15 | uint32_t RIFF; 16 | uint32_t next_chunk_size; 17 | uint32_t RIFF_type; 18 | } RIFF_chunk_t; 19 | 20 | #define WAV_FMT 0x20746D66 21 | typedef struct fmt_chunk_t { 22 | uint32_t fmt; 23 | uint32_t fmt_length; 24 | uint16_t wave_type; 25 | uint16_t channels; 26 | uint32_t sample_rate; 27 | uint32_t bytes_per_second; 28 | uint16_t block_alignment; 29 | uint16_t bit_resolution; 30 | } fmt_chunk_t; 31 | 32 | #define WAV_DATA 0x61746164 33 | typedef struct data_chunk_t { 34 | uint32_t data; 35 | uint32_t data_length; 36 | } data_chunk_t; 37 | 38 | // ISA DMA 39 | #define DMA_MASK_ON 0x04 40 | static const uint8_t DMA_SINGLE_CHANNEL_MASK[2] = {0x0A, 0xD4}; 41 | static const uint8_t DMA_FLIPFLOP_RESET[2] = {0x0C, 0xD8}; 42 | static const uint8_t DMA_MODE[2] = {0x0B, 0xD6}; 43 | static const uint8_t DMA_STATUS[2] = {0x08, 0xD0}; 44 | static const uint8_t DMA_PAGE_ADDRESS[8] = 45 | {0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A}; 46 | static const uint8_t DMA_START_ADDRESS[8] = 47 | {0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC}; 48 | static const uint8_t DMA_COUNT[8] = 49 | {0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE}; 50 | 51 | // Sound Blaster 16 52 | // Mixer register status 53 | #define SB_IRQ_2 0x1 54 | #define SB_IRQ_5 0x2 55 | #define SB_IRQ_7 0x4 56 | #define SB_IRQ_10 0x8 57 | 58 | #define SB_DMA_0 0x01 59 | #define SB_DMA_1 0x02 60 | #define SB_DMA_3 0x08 61 | #define SB_DMA_5 0x20 62 | #define SB_DMA_6 0x40 63 | #define SB_DMA_7 0x80 64 | 65 | // Mixer ports 66 | #define MIXER_ADDRESS_PORT 0x04 67 | #define MIXER_DATA_PORT 0x05 68 | 69 | #define MIXER_RESET_CMD 0x00 70 | #define MIXER_READ_IRQ_PORT 0x80 71 | #define MIXER_READ_DMA_PORT 0x81 72 | 73 | // Interrupt status 74 | #define MIXER_INT_STATUS_PORT 0x82 75 | #define MIXER_INT_STATUS_DMA_8BIT 0x01 76 | #define MIXER_INT_STATUS_DMA_16BIT 0x02 77 | 78 | // DSP commands 79 | #define DSP_DAC_SPEAKER_TURN_ON 0xD1 80 | #define DSP_DAC_SPEAKER_TURN_OFF 0xD3 81 | #define DSP_PAUSE_DMA_MODE 0xD0 82 | #define DSP_EXIT_AUTO_DMA_MODE_8 0xDA 83 | #define DSP_EXIT_AUTO_DMA_MODE_16 0xD9 84 | #define DSP_CMD_VERSION 0xE1 85 | 86 | #define DSP_PLAY_AUTOINIT_8BIT 0xC6 87 | #define DSP_PLAY_AUTOINIT_16BIT 0xB6 88 | #define DSP_PLAY_SCT_8BIT 0xC0 89 | #define DSP_PLAY_SCT_16BIT 0xB0 90 | #define DSP_SET_SAMPLE_RATE 0x41 91 | 92 | // DSP format 93 | #define DSP_FORMAT_8BIT_MONO 0x00 94 | #define DSP_FORMAT_8BIT_STEREO 0x20 95 | #define DSP_FORMAT_16BIT_MONO 0x10 96 | #define DSP_FORMAT_16BIT_STEREO 0x30 97 | 98 | // DSP response 99 | #define DSP_RESET_SUCCESS 0xAA 100 | #define DSP_DATA_IN_BUFFER 0x80 101 | 102 | // DSP ports 103 | #define DSP_ADDR_WIRTE 0x0C 104 | #define DSP_ADDR_READ_DATA 0x0A 105 | #define DSP_ADDR_READ_STATUS_8BIT 0x0E 106 | #define DSP_ADDR_READ_STATUS_16BIT 0x0F 107 | #define DSP_ADDR_RESET 0x06 108 | 109 | // Device info and sound system state 110 | static struct device_struct { 111 | bool enabled; 112 | uint16_t base; // Sound Blaster base address 113 | uint8_t DMA8_channel; // DMA channel (8bit) 114 | uint8_t DMA16_channel; // DMA channel (16bit) 115 | } device; 116 | 117 | // DMA buffer 118 | #define DMA_BUFFER_ADDRESS 0x70000 // Linear memory address 119 | static uint8_t *DMA_buffer = (uint8_t*)DMA_BUFFER_ADDRESS; 120 | const uint16_t DMA_buffer_size = 0x2000; // Bytes 121 | static const uint16_t buffer_address_high = DMA_BUFFER_ADDRESS >> 16; 122 | static const uint16_t buffer_address_low = DMA_BUFFER_ADDRESS & 0xFFFF; 123 | 124 | // Currently playing wav file 125 | static struct playing_file_struct { 126 | char path[MAX_PATH]; 127 | uint pos; 128 | uint bits; 129 | uint rate; 130 | uint channels; 131 | uint bytes_per_sample; 132 | uint length_seconds; 133 | } playing_file; 134 | 135 | static struct play_state_struct { 136 | volatile int remaining_samples; // Amount of samples to be played 137 | volatile int read_remaining_bytes; // Amount of bytes to be read 138 | volatile bool is_playing; 139 | uint started_time_seconds; 140 | uint16_t read_buffer_half; // 0=First half 1=Second half 141 | } play_state; 142 | 143 | // Read a byte from the mixer on the Sound Blaster 144 | static void sb_write_mixer(uint8_t addr, uint8_t value) 145 | { 146 | outb(device.base + MIXER_ADDRESS_PORT, addr); 147 | outb(device.base + MIXER_DATA_PORT, value); 148 | } 149 | 150 | // Read a byte from the mixer on the Sound Blaster 151 | static uint8_t sb_read_mixer(uint8_t address) 152 | { 153 | outb(device.base + MIXER_ADDRESS_PORT, address); 154 | return inb(device.base + MIXER_DATA_PORT); 155 | } 156 | 157 | // Try to reset a Sound Blaster at a given address 158 | // Return true if succesful, false otherwise 159 | static bool sb_reset_DSP(uint16_t addr) 160 | { 161 | // Reset the DSP 162 | outb(addr + DSP_ADDR_RESET, 1); 163 | wait(10); 164 | outb(addr + DSP_ADDR_RESET, 0); 165 | wait(10); 166 | 167 | // Check if reset was succesfull 168 | if((inb(addr + DSP_ADDR_READ_STATUS_8BIT) & DSP_DATA_IN_BUFFER) && 169 | (inb(addr + DSP_ADDR_READ_DATA) == DSP_RESET_SUCCESS)) 170 | { 171 | // DSP was found 172 | return TRUE; 173 | } 174 | 175 | // DSP was not found 176 | return FALSE; 177 | } 178 | 179 | // Write a byte to the DSP on the Sound Blaster 180 | static void sb_write_DSP(uint8_t value) 181 | { 182 | // Wait for the DSP to be ready to accept data 183 | while(inb(device.base + DSP_ADDR_WIRTE) & DSP_DATA_IN_BUFFER); 184 | // Write byte 185 | outb(device.base + DSP_ADDR_WIRTE, value); 186 | } 187 | 188 | // Read a byte from the DSP on the Sound Blaster 189 | static uint8_t sb_read_DSP() 190 | { 191 | // Wait for the DSP to be ready 192 | while(!(inb(device.base + DSP_ADDR_READ_STATUS_8BIT) & DSP_DATA_IN_BUFFER)); 193 | // Read byte 194 | return inb(device.base + DSP_ADDR_READ_DATA); 195 | } 196 | 197 | // Try to find a Sound Blaster 198 | static void sb_find() 199 | { 200 | // Nothing found yet 201 | device.base = 0; 202 | 203 | // Check for Sound Blaster at ports 204 | // 210, 220, 230, 240, 250, 260 and 280 205 | for(uint8_t i = 1; i < 9; i++) { 206 | if(i != 7) { 207 | const uint16_t addr = 0x200 + (i << 4); 208 | if(sb_reset_DSP(addr)) { 209 | device.base = addr; 210 | break; 211 | } 212 | } 213 | } 214 | } 215 | 216 | // Return true if a Sound Blaster has been found 217 | static bool sb_found() 218 | { 219 | return device.base != 0; 220 | } 221 | 222 | // Convert bytes to samples 223 | static uint bytes_to_samples(uint bytes) 224 | { 225 | return bytes/playing_file.bytes_per_sample; 226 | } 227 | 228 | // Load one half of the DMA buffer from the file 229 | static void read_buffer(uint8_t half_index) 230 | { 231 | const size_t half_buff_size = DMA_buffer_size / 2; 232 | 233 | if(play_state.read_remaining_bytes <= 0) { 234 | return; 235 | } 236 | uint8_t *buff = DMA_buffer + half_buff_size * half_index; 237 | 238 | // If the remaining part of the file is smaller than half the size 239 | // of the buffer, load it and fill out with silence 240 | if(play_state.read_remaining_bytes < (int)half_buff_size) { 241 | 242 | const uint8_t zero_value = 243 | playing_file.bits == 8 ? 0x80 : 0; 244 | memset(buff, zero_value, half_buff_size); 245 | 246 | const uint result = fs_read_file(buff, playing_file.path, 247 | playing_file.pos, play_state.read_remaining_bytes); 248 | if((int)result != play_state.read_remaining_bytes) { 249 | debug_putstr("Sound: Can't read wave file data at %d\n", 250 | playing_file.pos); 251 | } 252 | playing_file.pos += play_state.read_remaining_bytes; 253 | play_state.read_remaining_bytes = 0; 254 | 255 | } else { 256 | 257 | const uint result = fs_read_file(buff, playing_file.path, 258 | playing_file.pos, half_buff_size); 259 | if(result != half_buff_size) { 260 | debug_putstr("Sound: Can't read wave file data at %d\n", 261 | playing_file.pos); 262 | } 263 | playing_file.pos += half_buff_size; 264 | play_state.read_remaining_bytes -= half_buff_size; 265 | } 266 | } 267 | 268 | // Program the DMA controller. The DSP is instructed to play blocks of 269 | // half the total buffer size and then generate an interrupt 270 | // which allows the program to load the next part that should be played 271 | static void sb_auto_init_playback() 272 | { 273 | const uint8_t DMA_channel = 274 | playing_file.bits == 8 ? device.DMA8_channel : 275 | playing_file.bits == 16 ? device.DMA16_channel : 276 | 0; 277 | 278 | const uint8_t bits = 279 | playing_file.bits == 8 ? 0 : 280 | playing_file.bits == 16 ? 1 : 281 | 0; 282 | 283 | const uint8_t channel_mask = DMA_channel % 4; 284 | 285 | const uint16_t buff_addr_low = 286 | playing_file.bits == 8 ? buffer_address_low : 287 | playing_file.bits == 16 ? buffer_address_low >> 1 : 288 | 0; 289 | 290 | // Mask DMA channel 291 | outb(DMA_SINGLE_CHANNEL_MASK[bits], DMA_MASK_ON|channel_mask); 292 | outb(DMA_FLIPFLOP_RESET[bits], 0); // Clear byte pointer 293 | outb(DMA_MODE[bits], 0x58 | channel_mask); // Set mode 294 | outb(DMA_START_ADDRESS[DMA_channel], buff_addr_low & 0xFF); // Write offset 295 | outb(DMA_START_ADDRESS[DMA_channel], buff_addr_low >> 8); 296 | 297 | /* 298 | The mode consists of the following: 299 | 0x58+x = binary 01 01 10 xx 300 | | | | | 301 | | | | +- DMA channel 302 | | | +---- Read operation (the DSP reads from memory) 303 | | +------- Auto init mode 304 | +---------- Block mode 305 | */ 306 | 307 | // Write the page to the DMA controller 308 | outb(DMA_PAGE_ADDRESS[DMA_channel], buffer_address_high); 309 | 310 | // Set the block length to buffer size 311 | const uint16_t DMA_buff_size = 312 | playing_file.bits == 8 ? DMA_buffer_size-1 : 313 | playing_file.bits == 16 ? (DMA_buffer_size/2)-1 : 314 | 0; 315 | 316 | outb(DMA_COUNT[DMA_channel], DMA_buff_size & 0xFF); 317 | outb(DMA_COUNT[DMA_channel], DMA_buff_size >> 8); 318 | outb(DMA_SINGLE_CHANNEL_MASK[bits], channel_mask); // Unmask DMA channel 319 | 320 | // Set sample rate 321 | sb_write_DSP(DSP_SET_SAMPLE_RATE); 322 | sb_write_DSP(playing_file.rate >> 8); 323 | sb_write_DSP(playing_file.rate & 0xFF); 324 | 325 | // Set the block length to half buffer size minus one 326 | const uint16_t buffer_size = 327 | playing_file.bits == 8 ? (DMA_buffer_size/2)-1 : 328 | playing_file.bits == 16 ? (DMA_buffer_size/4)-1 : 329 | 0; 330 | 331 | const uint8_t command = 332 | playing_file.bits == 8 ? DSP_PLAY_AUTOINIT_8BIT : 333 | playing_file.bits == 16 ? DSP_PLAY_AUTOINIT_16BIT : 334 | 0; 335 | const uint8_t format = 336 | playing_file.channels == 1 ? 337 | playing_file.bits == 8 ? DSP_FORMAT_8BIT_MONO : 338 | playing_file.bits == 16 ? DSP_FORMAT_16BIT_MONO : 0 : 339 | playing_file.channels == 2 ? 340 | playing_file.bits == 8 ? DSP_FORMAT_8BIT_STEREO : 341 | playing_file.bits == 16 ? DSP_FORMAT_16BIT_STEREO : 0 : 342 | 0; 343 | 344 | sb_write_DSP(command); 345 | sb_write_DSP(format); 346 | sb_write_DSP(buffer_size & 0xFF); 347 | sb_write_DSP(buffer_size >> 8); 348 | } 349 | 350 | // Program the DMA controller to play a single block 351 | static void sb_single_cycle_playback() 352 | { 353 | const uint8_t DMA_channel = 354 | playing_file.bits == 8 ? device.DMA8_channel : 355 | playing_file.bits == 16 ? device.DMA16_channel : 356 | 0; 357 | 358 | const uint8_t bits = 359 | playing_file.bits == 8 ? 0 : 360 | playing_file.bits == 16 ? 1 : 361 | 0; 362 | 363 | const uint8_t channel_mask = DMA_channel % 4; 364 | 365 | const uint16_t buff_offset = 366 | buffer_address_low + play_state.read_buffer_half * DMA_buffer_size/2; 367 | 368 | const uint16_t buff_addr_low = 369 | playing_file.bits == 8 ? buff_offset : 370 | playing_file.bits == 16 ? buff_offset : 371 | 0; 372 | 373 | // Mask DMA channel 374 | outb(DMA_SINGLE_CHANNEL_MASK[bits], DMA_MASK_ON|channel_mask); 375 | outb(DMA_FLIPFLOP_RESET[bits], 0); // Clear byte pointer 376 | outb(DMA_MODE[bits], 0x48 | channel_mask); // Set mode 377 | outb(DMA_START_ADDRESS[DMA_channel], buff_addr_low & 0xFF); 378 | outb(DMA_START_ADDRESS[DMA_channel], buff_addr_low >> 8); // Write offset 379 | 380 | /* 381 | The mode consists of the following: 382 | 0x48+x = binary 01 00 10 xx 383 | | | | | 384 | | | | +- DMA channel 385 | | | +---- Read operation (the DSP reads from memory) 386 | | +------- Single cycle mode 387 | +---------- Block mode 388 | */ 389 | 390 | // Write the page to the DMA controller 391 | outb(DMA_PAGE_ADDRESS[DMA_channel], buffer_address_high); 392 | 393 | // Set the block length 394 | play_state.remaining_samples--; // DMA needs this (see specification) 395 | outb(DMA_COUNT[DMA_channel], play_state.remaining_samples & 0xFF); 396 | outb(DMA_COUNT[DMA_channel], play_state.remaining_samples >> 8); 397 | outb(DMA_SINGLE_CHANNEL_MASK[bits], channel_mask); // Unmask DMA channel 398 | 399 | // Set sample rate 400 | sb_write_DSP(DSP_SET_SAMPLE_RATE); 401 | sb_write_DSP(playing_file.rate >> 8); 402 | sb_write_DSP(playing_file.rate & 0xFF); 403 | 404 | // DSP command single cycle playback 405 | const uint8_t command = 406 | playing_file.bits == 8 ? DSP_PLAY_SCT_8BIT : 407 | playing_file.bits == 16 ? DSP_PLAY_SCT_16BIT : 408 | 0; 409 | const uint8_t format = 410 | playing_file.channels == 1 ? 411 | playing_file.bits == 8 ? DSP_FORMAT_8BIT_MONO : 412 | playing_file.bits == 16 ? DSP_FORMAT_16BIT_MONO : 0 : 413 | playing_file.channels == 2 ? 414 | playing_file.bits == 8 ? DSP_FORMAT_8BIT_STEREO : 415 | playing_file.bits == 16 ? DSP_FORMAT_16BIT_STEREO : 0 : 416 | 0; 417 | 418 | const uint16_t remaining_samples = 419 | playing_file.bits == 8 ? 420 | play_state.remaining_samples/playing_file.channels : 421 | (uint16_t)play_state.remaining_samples; 422 | 423 | sb_write_DSP(command); 424 | sb_write_DSP(format); 425 | sb_write_DSP(remaining_samples & 0xFF); 426 | sb_write_DSP(remaining_samples >> 8); 427 | 428 | // Nothing left to play 429 | play_state.remaining_samples = 0; 430 | } 431 | 432 | // IRQ service routine 433 | // Called when the DSP has finished playing a block 434 | void sound_handler() 435 | { 436 | disable_interrupts(); 437 | 438 | const uint8_t interrupt_status = 439 | sb_read_mixer(MIXER_INT_STATUS_PORT); 440 | 441 | debug_putstr("Sound: Handling interruption (%2x)\n", 442 | interrupt_status); 443 | 444 | if(io_sound_is_enabled()) { 445 | // Take appropriate action 446 | if(play_state.is_playing) { 447 | const int num_samples_in_buffer = bytes_to_samples(DMA_buffer_size); 448 | 449 | play_state.remaining_samples -= num_samples_in_buffer/2; 450 | if(play_state.remaining_samples > 0) { 451 | 452 | read_buffer(play_state.read_buffer_half); 453 | if(play_state.remaining_samples <= num_samples_in_buffer/2) { 454 | play_state.read_buffer_half ^= 1; 455 | sb_single_cycle_playback(); 456 | 457 | } else if(play_state.remaining_samples <= num_samples_in_buffer) { 458 | if(playing_file.bits == 8) { 459 | sb_write_DSP(DSP_EXIT_AUTO_DMA_MODE_8); 460 | } else if(playing_file.bits == 16) { 461 | sb_write_DSP(DSP_EXIT_AUTO_DMA_MODE_16); 462 | } 463 | } 464 | play_state.read_buffer_half ^= 1; 465 | } else { 466 | play_state.is_playing = FALSE; 467 | debug_putstr("Sound: Play sound %s finished\n", 468 | playing_file.path); 469 | } 470 | } 471 | 472 | // Acknowledge to DSP 473 | if(interrupt_status & MIXER_INT_STATUS_DMA_8BIT) { 474 | inb(device.base + DSP_ADDR_READ_STATUS_8BIT); 475 | } 476 | if(interrupt_status & MIXER_INT_STATUS_DMA_16BIT) { 477 | inb(device.base + DSP_ADDR_READ_STATUS_16BIT); 478 | } 479 | } 480 | 481 | // Acknowledge hardware interrupt 482 | lapic_eoi(); 483 | enable_interrupts(); 484 | } 485 | 486 | // Return true if a sound is still playing 487 | bool io_sound_is_playing() 488 | { 489 | if(play_state.is_playing) { 490 | const uint time_seconds = io_gettimer() / 1000; 491 | const uint time_elapsed = time_seconds - play_state.started_time_seconds; 492 | if(time_elapsed > playing_file.length_seconds) { 493 | debug_putstr("Sound: Forced sound stop (%s). lenght=%us elapsed=%us\n", 494 | playing_file.path, playing_file.length_seconds, time_elapsed); 495 | io_sound_stop(); 496 | } 497 | } 498 | return play_state.is_playing; 499 | } 500 | 501 | // Stop playing sound 502 | void io_sound_stop() 503 | { 504 | // Stop DMA transfer 505 | if(io_sound_is_enabled()) { 506 | sb_write_DSP(DSP_PAUSE_DMA_MODE); 507 | sb_write_DSP(DSP_DAC_SPEAKER_TURN_OFF); 508 | } 509 | 510 | play_state.is_playing = FALSE; 511 | } 512 | 513 | // Start playing a WAV file 514 | uint sb_play(const char *wav_file_path) 515 | { 516 | memset(playing_file.path, 0, sizeof(playing_file.path)); 517 | strncpy(playing_file.path, wav_file_path, 518 | sizeof(playing_file.path)); 519 | 520 | // Start playback in buffer 0 and clear the buffer 521 | play_state.read_buffer_half = 0; 522 | memset(DMA_buffer, 0, DMA_buffer_size); 523 | 524 | // Read RIFF chunk 525 | RIFF_chunk_t RIFF_chunk = {0}; 526 | uint result = 527 | fs_read_file(&RIFF_chunk, playing_file.path, 0, sizeof(RIFF_chunk)); 528 | 529 | if(result != sizeof(RIFF_chunk) || 530 | RIFF_chunk.RIFF != WAV_RIFF || 531 | RIFF_chunk.RIFF_type != WAV_WAVE) { 532 | debug_putstr("Sound: Can't read wave file RIFF (%s)\n", 533 | playing_file.path); 534 | 535 | return ERROR_IO; 536 | } 537 | playing_file.pos = sizeof(RIFF_chunk); 538 | 539 | // Read fmt chunk 540 | fmt_chunk_t fmt_chunk = {0}; 541 | do { 542 | result = fs_read_file(&fmt_chunk, playing_file.path, 543 | playing_file.pos, sizeof(fmt_chunk)); 544 | 545 | if(result != sizeof(fmt_chunk)) { 546 | debug_putstr("Sound: Can't read wave file fmt (%s)\n", 547 | playing_file.path); 548 | 549 | return ERROR_IO; 550 | } 551 | playing_file.pos += fmt_chunk.fmt_length + 8; 552 | } while(fmt_chunk.fmt != WAV_FMT); 553 | 554 | // Set format 555 | playing_file.bits = fmt_chunk.bit_resolution; 556 | if(playing_file.bits != 8 && playing_file.bits != 16) { 557 | debug_putstr("Sound: Unsupported bit depth (%s,%d)\n", 558 | playing_file.path, playing_file.bits); 559 | return ERROR_IO; 560 | } 561 | 562 | playing_file.rate = fmt_chunk.sample_rate; 563 | playing_file.channels = fmt_chunk.channels; 564 | if(playing_file.channels != 1 && 565 | playing_file.channels != 2) { 566 | debug_putstr("Sound: Unsupported number of channels (%s,%d)\n", 567 | playing_file.path, playing_file.channels); 568 | return ERROR_IO; 569 | } 570 | playing_file.bytes_per_sample = fmt_chunk.bit_resolution/8; 571 | 572 | // Read data chunk 573 | data_chunk_t data_chunk = {0}; 574 | do { 575 | result = fs_read_file(&data_chunk, playing_file.path, 576 | playing_file.pos, sizeof(data_chunk)); 577 | 578 | if(result != sizeof(data_chunk)) { 579 | debug_putstr("Sound: Can't read wave file data (%s)\n", 580 | playing_file.path); 581 | return ERROR_IO; 582 | } 583 | if(data_chunk.data != WAV_DATA) { 584 | playing_file.pos += data_chunk.data_length + 8; 585 | } 586 | } while(data_chunk.data != WAV_DATA); 587 | play_state.read_remaining_bytes = data_chunk.data_length; 588 | play_state.remaining_samples = bytes_to_samples(data_chunk.data_length); 589 | playing_file.length_seconds = 1 + 590 | (play_state.remaining_samples / playing_file.channels) / 591 | playing_file.rate; 592 | 593 | debug_putstr("Sound: Read wave file data (%s, %d bytes)\n", 594 | playing_file.path, play_state.read_remaining_bytes); 595 | 596 | disable_interrupts(); 597 | 598 | // Enable speaker 599 | sb_write_DSP(DSP_DAC_SPEAKER_TURN_ON); 600 | 601 | // Read first bit of data 602 | read_buffer(0); 603 | read_buffer(1); 604 | 605 | if(play_state.read_remaining_bytes > 0) { 606 | debug_putstr("Sound: Auto init playback (%s) %u seconds " 607 | "samples=%d bytes=%d bytes/sample=%d channels=%d\n", 608 | playing_file.path, playing_file.length_seconds, 609 | play_state.remaining_samples, play_state.read_remaining_bytes, 610 | playing_file.bytes_per_sample, playing_file.channels); 611 | 612 | sb_auto_init_playback(); 613 | } else { 614 | debug_putstr("Sound: Single cycle playback (%s) %u seconds\n,", 615 | playing_file.path, playing_file.length_seconds); 616 | 617 | sb_single_cycle_playback(); 618 | } 619 | 620 | // Check if the sound is actually playing 621 | const uint8_t bits = 622 | playing_file.bits == 8 ? 0 : 623 | playing_file.bits == 16 ? 1 : 624 | 0; 625 | const uint8_t status = inb(DMA_STATUS[bits]); 626 | if(status & 0xF0) { 627 | play_state.is_playing = TRUE; 628 | play_state.started_time_seconds = io_gettimer() / 1000; 629 | } else { 630 | debug_putstr("Sound: Couldn't initialize DMA\n"); 631 | io_sound_stop(); 632 | } 633 | enable_interrupts(); 634 | return NO_ERROR; 635 | } 636 | 637 | // Initialize sound driver 638 | void io_sound_init() 639 | { 640 | device.enabled = FALSE; 641 | play_state.read_buffer_half = 0; 642 | play_state.is_playing = FALSE; 643 | 644 | // Check for Sound Blaster 645 | sb_find(); 646 | if(!sb_found()) { 647 | debug_putstr("Sound: Sound Blaster not found\n"); 648 | return; 649 | } 650 | 651 | // Get IRQ 652 | uint8_t IRQ = 0; 653 | const uint read_IRQ = sb_read_mixer(MIXER_READ_IRQ_PORT); 654 | switch(read_IRQ) { 655 | case SB_IRQ_2: IRQ = 2; break; 656 | case SB_IRQ_5: IRQ = 5; break; 657 | case SB_IRQ_7: IRQ = 7; break; 658 | }; 659 | if(IRQ == 0) { 660 | debug_putstr("Sound: Failed to get Sound Blaster IRQ\n"); 661 | return; 662 | } 663 | 664 | // Get 8bit DMA channel 665 | device.DMA8_channel = 10; 666 | const uint read_DMA = sb_read_mixer(MIXER_READ_DMA_PORT); 667 | if(read_DMA & SB_DMA_0) { 668 | device.DMA8_channel = 0; 669 | } else if(read_DMA & SB_DMA_1) { 670 | device.DMA8_channel = 1; 671 | } else if(read_DMA & SB_DMA_3) { 672 | device.DMA8_channel = 3; 673 | } 674 | // Get 16bit DMA channel 675 | device.DMA16_channel = 10; 676 | if(read_DMA & SB_DMA_5) { 677 | device.DMA16_channel = 5; 678 | } else if(read_DMA & SB_DMA_6) { 679 | device.DMA16_channel = 6; 680 | } else if(read_DMA & SB_DMA_7) { 681 | device.DMA16_channel = 7; 682 | } 683 | if(device.DMA8_channel > 3 || device.DMA16_channel > 7) { 684 | debug_putstr("Sound: Failed to get Sound Blaster DMA\n"); 685 | return; 686 | } 687 | 688 | // Install IRQ handler 689 | set_sound_IRQ(IRQ); 690 | 691 | sb_write_DSP(DSP_DAC_SPEAKER_TURN_OFF); 692 | 693 | // Get DSP version 694 | sb_write_DSP(DSP_CMD_VERSION); 695 | const uint version_low = sb_read_DSP(); 696 | const uint version_high = sb_read_DSP(); 697 | debug_putstr("Sound: Sound Blaster found at %x " 698 | "(IRQ=%d DMA=%d,%d) DSP v%d.%d\n", 699 | device.base, IRQ, device.DMA8_channel, device.DMA16_channel, 700 | version_low, version_high); 701 | 702 | sb_write_mixer(MIXER_RESET_CMD, 0x00); // Reset mixer 703 | 704 | device.enabled = TRUE; 705 | } 706 | 707 | // Play wave file 708 | uint io_sound_play(const char *wav_file_path) 709 | { 710 | if(io_sound_is_enabled()) { 711 | io_sound_stop(); 712 | return sb_play(wav_file_path); 713 | } 714 | return ERROR_NOT_AVAILABLE; 715 | } 716 | 717 | // Is sound enabled? 718 | bool io_sound_is_enabled() 719 | { 720 | return device.enabled; 721 | } -------------------------------------------------------------------------------- /source/programs/nas.c: -------------------------------------------------------------------------------- 1 | // User program: Intel 80386 Assembler 2 | 3 | #include "types.h" 4 | #include "ulib/ulib.h" 5 | 6 | #define EOF 0xFFFFFFE0 // End Of File 7 | 8 | // Code generation vars 9 | uint cg_origin = 0x20000; // Absolute pos 10 | 11 | // Symbol types 12 | enum S_TYPE { 13 | S_LABEL, // Label 14 | S_DATAD, // Data double 15 | S_DATAW, // Data word 16 | S_DATAB // Data byte 17 | }; 18 | 19 | // Symbols table 20 | #define S_MAX 32 21 | struct symbol { 22 | char name[8]; 23 | uint type; // See enum S_TYPE 24 | uint value; 25 | } s_table[S_MAX]; 26 | 27 | // Operand types 28 | enum O_TYPE { 29 | O_ANY, // unknown 30 | O_RD, // Register double 31 | O_RMD, // Memory double in a register 32 | O_MD, // Memory double immediate 33 | O_IX, // Immediate 34 | }; 35 | 36 | // Registers 37 | enum R_DEF { 38 | R_ANY, // Unknown 39 | R_AX, 40 | R_CX, 41 | R_DX, 42 | R_BX, 43 | R_SP, 44 | R_BP, 45 | R_SI, 46 | R_DI, 47 | R_COUNT 48 | }; 49 | 50 | // CONST Registers data 51 | struct reg_data_struct { 52 | char name[5]; 53 | uint type; 54 | uint encoding; 55 | } const r_data[R_COUNT] = 56 | { 57 | {"NO", O_ANY, 0x00}, 58 | {"eax", O_RD, 0x00}, 59 | {"ecx", O_RD, 0x01}, 60 | {"edx", O_RD, 0x02}, 61 | {"ebx", O_RD, 0x03}, 62 | {"esp", O_RD, 0x04}, 63 | {"ebp", O_RD, 0x05}, 64 | {"esi", O_RD, 0x06}, 65 | {"edi", O_RD, 0x07}, 66 | }; 67 | 68 | // Instruction id 69 | enum I_DEF { 70 | I_PUSH, 71 | I_POP, 72 | I_MOV_RID, 73 | I_MOV_RRD, 74 | I_MOV_RMD, 75 | I_MOV_MRD, 76 | I_MOV_RRMD, 77 | I_MOV_RMRD, 78 | I_CMP_RID, 79 | I_CMP_RRD, 80 | I_CMP_RMD, 81 | I_CMP_MRD, 82 | I_CMP_RRMD, 83 | I_CMP_RMRD, 84 | I_RET, 85 | I_INT, 86 | I_CALL, 87 | I_JMP, 88 | I_JE, 89 | I_JNE, 90 | I_JG, 91 | I_JGE, 92 | I_JL, 93 | I_JLE, 94 | I_JC, 95 | I_JNC, 96 | I_ADD_RID, 97 | I_ADD_RRD, 98 | I_ADD_RMD, 99 | I_ADD_MRD, 100 | I_ADD_RRMD, 101 | I_ADD_RMRD, 102 | I_SUB_RID, 103 | I_SUB_RRD, 104 | I_SUB_RMD, 105 | I_SUB_MRD, 106 | I_SUB_RRMD, 107 | I_SUB_RMRD, 108 | I_MUL_RD, 109 | I_DIV_RD, 110 | I_NOT_RD, 111 | I_AND_RID, 112 | I_AND_RRD, 113 | I_AND_RMD, 114 | I_AND_MRD, 115 | I_AND_RRMD, 116 | I_AND_RMRD, 117 | I_OR_RID, 118 | I_OR_RRD, 119 | I_OR_RMD, 120 | I_OR_MRD, 121 | I_OR_RRMD, 122 | I_OR_RMRD, 123 | I_COUNT 124 | }; 125 | 126 | // CONST Instructions data 127 | #define I_MAX_OPS 2 // Max instruction operands 128 | typedef struct idata_t { 129 | char mnemonic[6]; 130 | uint opcode; 131 | uint nops; // Number of operands 132 | uint op_type[I_MAX_OPS]; // Type of operands 133 | uint op_value[I_MAX_OPS]; // Restricted value 134 | } idata_t; 135 | 136 | const idata_t i_data[I_COUNT] = 137 | { 138 | {"push", 0x50, 1, {O_RD, O_ANY}, {R_ANY, 0 }}, 139 | {"pop", 0x58, 1, {O_RD, O_ANY}, {R_ANY, 0 }}, 140 | {"mov", 0xB8, 2, {O_RD, O_IX }, {R_ANY, 0 }}, 141 | {"mov", 0x89, 2, {O_RD, O_RD }, {R_ANY, R_ANY}}, 142 | {"mov", 0x8B, 2, {O_RD, O_MD }, {R_ANY, 0 }}, 143 | {"mov", 0x89, 2, {O_MD, O_RD }, {0, R_ANY}}, 144 | {"mov", 0x8B, 2, {O_RD, O_RMD}, {R_ANY, R_ANY}}, 145 | {"mov", 0x89, 2, {O_RMD, O_RD }, {R_ANY, R_ANY}}, 146 | {"cmp", 0x81, 2, {O_RD, O_IX }, {R_ANY, 0 }}, 147 | {"cmp", 0x39, 2, {O_RD, O_RD }, {R_ANY, R_ANY}}, 148 | {"cmp", 0x3B, 2, {O_RD, O_MD }, {R_ANY, 0 }}, 149 | {"cmp", 0x39, 2, {O_MD, O_RD }, {0, R_ANY}}, 150 | {"cmp", 0x3B, 2, {O_RD, O_RMD}, {R_ANY, R_ANY}}, 151 | {"cmp", 0x39, 2, {O_RMD, O_RD }, {R_ANY, R_ANY}}, 152 | {"ret", 0xC3, 0, {O_ANY, O_ANY}, {0, 0 }}, 153 | {"int", 0xCD, 1, {O_IX, O_ANY}, {0, 0 }}, 154 | {"call", 0xE8, 1, {O_IX, O_ANY}, {0, 0 }}, 155 | {"jmp", 0xE9, 1, {O_IX, O_ANY}, {0, 0 }}, 156 | {"je", 0x74, 1, {O_IX, O_ANY}, {0, 0 }}, 157 | {"jne", 0x75, 1, {O_IX, O_ANY}, {0, 0 }}, 158 | {"jg", 0x7F, 1, {O_IX, O_ANY}, {0, 0 }}, 159 | {"jge", 0x7D, 1, {O_IX, O_ANY}, {0, 0 }}, 160 | {"jl", 0x7C, 1, {O_IX, O_ANY}, {0, 0 }}, 161 | {"jle", 0x7E, 1, {O_IX, O_ANY}, {0, 0 }}, 162 | {"jc", 0x72, 1, {O_IX, O_ANY}, {0, 0 }}, 163 | {"jnc", 0x73, 1, {O_IX, O_ANY}, {0, 0 }}, 164 | {"add", 0x81, 2, {O_RD, O_IX }, {R_ANY, 0 }}, 165 | {"add", 0x01, 2, {O_RD, O_RD }, {R_ANY, R_ANY}}, 166 | {"add", 0x03, 2, {O_RD, O_MD }, {R_ANY, 0 }}, 167 | {"add", 0x01, 2, {O_MD, O_RD }, {0, R_ANY}}, 168 | {"add", 0x03, 2, {O_RD, O_RMD}, {R_ANY, R_ANY}}, 169 | {"add", 0x01, 2, {O_RMD, O_RD }, {R_ANY, R_ANY}}, 170 | {"sub", 0x81, 2, {O_RD, O_IX }, {R_ANY, 0 }}, 171 | {"sub", 0x29, 2, {O_RD, O_RD }, {R_ANY, R_ANY}}, 172 | {"sub", 0x2B, 2, {O_RD, O_MD }, {R_ANY, 0 }}, 173 | {"sub", 0x29, 2, {O_MD, O_RD }, {0, R_ANY}}, 174 | {"sub", 0x2B, 2, {O_RD, O_RMD}, {R_ANY, R_ANY}}, 175 | {"sub", 0x29, 2, {O_RMD, O_RD }, {R_ANY, R_ANY}}, 176 | {"mul", 0xF7, 2, {O_RD, O_ANY}, {R_ANY, 0 }}, 177 | {"div", 0xF7, 2, {O_RD, O_ANY}, {R_ANY, 0 }}, 178 | {"not", 0xF7, 2, {O_RD, O_ANY}, {R_ANY, 0 }}, 179 | {"and", 0x81, 2, {O_RD, O_IX }, {R_ANY, 0 }}, 180 | {"and", 0x21, 2, {O_RD, O_RD }, {R_ANY, R_ANY}}, 181 | {"and", 0x23, 2, {O_RD, O_MD }, {R_ANY, 0 }}, 182 | {"and", 0x21, 2, {O_MD, O_RD }, {0, R_ANY}}, 183 | {"and", 0x23, 2, {O_RD, O_RMD}, {R_ANY, R_ANY}}, 184 | {"and", 0x21, 2, {O_RMD, O_RD }, {R_ANY, R_ANY}}, 185 | {"or", 0x81, 2, {O_RD, O_IX }, {R_ANY, 0 }}, 186 | {"or", 0x09, 2, {O_RD, O_RD }, {R_ANY, R_ANY}}, 187 | {"or", 0x0B, 2, {O_RD, O_MD }, {R_ANY, 0 }}, 188 | {"or", 0x09, 2, {O_MD, O_RD }, {0, R_ANY}}, 189 | {"or", 0x0B, 2, {O_RD, O_RMD}, {R_ANY, R_ANY}}, 190 | {"or", 0x09, 2, {O_RMD, O_RD }, {R_ANY, R_ANY}}, 191 | }; 192 | 193 | // Symbols reference table 194 | #define S_MAX_REF 32 195 | struct s_ref_struct { 196 | uint offset; 197 | uint symbol; 198 | uint operand; 199 | uint instr_id; 200 | idata_t instruction; 201 | } s_ref[S_MAX_REF]; 202 | 203 | // Is string uint? 204 | bool sisu(char *src) 205 | { 206 | uint base = 10; 207 | 208 | if(!src[0]) { 209 | return FALSE; 210 | } 211 | 212 | // Is hex? 213 | if(src[0]=='0' && src[1]=='x' && src[2]) { 214 | src += 2; 215 | base = 16; 216 | } 217 | 218 | while(*src) { 219 | uint digit = *src<='9'? *src-'0' : 220 | *src>='a'? 0xA+*src-'a' : 0xA+*src-'A'; 221 | 222 | if(digit>=base) { 223 | return FALSE; 224 | } 225 | src++; 226 | } 227 | return TRUE; 228 | } 229 | 230 | // Encode and write instruction to buffer 231 | uint encode_instruction(uint8_t *buffer, uint offset, uint id, uint *op) 232 | { 233 | switch(id) { 234 | 235 | case I_RET: { 236 | buffer[offset++] = i_data[id].opcode; 237 | debug_putstr(": %2x", buffer[offset-1]); 238 | break; 239 | } 240 | 241 | case I_PUSH: 242 | case I_POP: { 243 | buffer[offset++] = 244 | i_data[id].opcode + r_data[op[0]].encoding; 245 | 246 | debug_putstr(": %2x", buffer[offset-1]); 247 | break; 248 | } 249 | 250 | case I_INT: { 251 | buffer[offset++] = i_data[id].opcode; 252 | buffer[offset++] = op[0]; 253 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 254 | break; 255 | } 256 | 257 | case I_JG: 258 | case I_JGE: 259 | case I_JL: 260 | case I_JLE: 261 | case I_JC: 262 | case I_JNC: 263 | case I_JE: 264 | case I_JNE: { 265 | buffer[offset++] = i_data[id].opcode; 266 | buffer[offset] = (op[0]-cg_origin-(offset+1)); 267 | offset++; 268 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 269 | break; 270 | } 271 | 272 | case I_CALL: 273 | case I_JMP: { 274 | uint address = op[0]-cg_origin-(offset+5); 275 | buffer[offset++] = i_data[id].opcode; 276 | buffer[offset++] = address & 0xFF; 277 | buffer[offset++] = (address >> 8 ) & 0xFF; 278 | buffer[offset++] = (address >> 16) & 0xFF; 279 | buffer[offset++] = (address >> 24) & 0xFF; 280 | debug_putstr(": %2x %2x %2x %2x %2x", 281 | buffer[offset-5], buffer[offset-4], buffer[offset-3], 282 | buffer[offset-2], buffer[offset-1]); 283 | break; 284 | } 285 | 286 | case I_OR_RID: { 287 | buffer[offset++] = i_data[id].opcode; 288 | buffer[offset++] = 0xC0|(0x01<<3)|r_data[op[0]].encoding; 289 | buffer[offset++] = op[1] & 0xFF; 290 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 291 | buffer[offset++] = (op[1] >> 16) & 0xFF; 292 | buffer[offset++] = (op[1] >> 24) & 0xFF; 293 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 294 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 295 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 296 | break; 297 | } 298 | 299 | case I_AND_RID: { 300 | buffer[offset++] = i_data[id].opcode; 301 | buffer[offset++] = 0xC0|(0x04<<3)|r_data[op[0]].encoding; 302 | buffer[offset++] = op[1] & 0xFF; 303 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 304 | buffer[offset++] = (op[1] >> 16) & 0xFF; 305 | buffer[offset++] = (op[1] >> 24) & 0xFF; 306 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 307 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 308 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 309 | break; 310 | } 311 | 312 | case I_NOT_RD: { 313 | buffer[offset++] = i_data[id].opcode; 314 | buffer[offset++] = 0xC0|(0x02<<3)|r_data[op[0]].encoding; 315 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 316 | break; 317 | } 318 | 319 | case I_MUL_RD: { 320 | buffer[offset++] = i_data[id].opcode; 321 | buffer[offset++] = 0xC0|(0x04<<3)|r_data[op[0]].encoding; 322 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 323 | break; 324 | } 325 | 326 | case I_DIV_RD: { 327 | buffer[offset++] = i_data[id].opcode; 328 | buffer[offset++] = 0xC0|(0x06<<3)|r_data[op[0]].encoding; 329 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 330 | break; 331 | } 332 | 333 | case I_SUB_RID: { 334 | buffer[offset++] = i_data[id].opcode; 335 | buffer[offset++] = 0xC0|(0x05<<3)|r_data[op[0]].encoding; 336 | buffer[offset++] = op[1] & 0xFF; 337 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 338 | buffer[offset++] = (op[1] >> 16) & 0xFF; 339 | buffer[offset++] = (op[1] >> 24) & 0xFF; 340 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 341 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 342 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 343 | break; 344 | } 345 | 346 | case I_ADD_RID: { 347 | buffer[offset++] = i_data[id].opcode; 348 | buffer[offset++] = 0xC0|r_data[op[0]].encoding; 349 | buffer[offset++] = op[1] & 0xFF; 350 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 351 | buffer[offset++] = (op[1] >> 16) & 0xFF; 352 | buffer[offset++] = (op[1] >> 24) & 0xFF; 353 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 354 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 355 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 356 | break; 357 | } 358 | 359 | case I_CMP_RID: { 360 | buffer[offset++] = i_data[id].opcode; 361 | buffer[offset++] = 0xC0|(0x07<<3)|r_data[op[0]].encoding; 362 | buffer[offset++] = op[1] & 0xFF; 363 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 364 | buffer[offset++] = (op[1] >> 16) & 0xFF; 365 | buffer[offset++] = (op[1] >> 24) & 0xFF; 366 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 367 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 368 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 369 | break; 370 | } 371 | 372 | case I_MOV_RID: { 373 | buffer[offset++] = i_data[id].opcode + r_data[op[0]].encoding; 374 | buffer[offset++] = op[1] & 0xFF; 375 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 376 | buffer[offset++] = (op[1] >> 16) & 0xFF; 377 | buffer[offset++] = (op[1] >> 24) & 0xFF; 378 | debug_putstr(": %2x %2x %2x %2x %2x", 379 | buffer[offset-5], buffer[offset-4], buffer[offset-3], 380 | buffer[offset-2], buffer[offset-1]); 381 | break; 382 | } 383 | 384 | case I_MOV_RMD: { 385 | buffer[offset++] = i_data[id].opcode; 386 | buffer[offset++] = 0x05|(r_data[op[0]].encoding<<3); 387 | buffer[offset++] = op[1] & 0xFF; 388 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 389 | buffer[offset++] = (op[1] >> 16) & 0xFF; 390 | buffer[offset++] = (op[1] >> 24) & 0xFF; 391 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 392 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 393 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 394 | break; 395 | } 396 | 397 | case I_CMP_MRD: { 398 | buffer[offset++] = i_data[id].opcode; 399 | buffer[offset++] = 0x05|(r_data[op[1]].encoding<<3); 400 | buffer[offset++] = op[1] & 0xFF; 401 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 402 | buffer[offset++] = (op[1] >> 16) & 0xFF; 403 | buffer[offset++] = (op[1] >> 24) & 0xFF; 404 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 405 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 406 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 407 | break; 408 | } 409 | 410 | case I_CMP_RMRD: { 411 | buffer[offset++] = i_data[id].opcode; 412 | buffer[offset++] = (r_data[op[1]].encoding<<3)|r_data[op[0]].encoding; 413 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 414 | break; 415 | } 416 | 417 | case I_OR_RRD: 418 | case I_AND_RRD: 419 | case I_SUB_RRD: 420 | case I_ADD_RRD: 421 | case I_CMP_RRD: 422 | case I_MOV_RRD: { 423 | buffer[offset++] = i_data[id].opcode; 424 | buffer[offset++] = 0xC0|(r_data[op[1]].encoding<<3)|r_data[op[0]].encoding; 425 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 426 | break; 427 | } 428 | 429 | case I_OR_RMD: 430 | case I_AND_RMD: 431 | case I_SUB_RMD: 432 | case I_ADD_RMD: 433 | case I_CMP_RMD: { 434 | buffer[offset++] = i_data[id].opcode; 435 | buffer[offset++] = 0x05|(r_data[op[0]].encoding<<3); 436 | buffer[offset++] = op[1] & 0xFF; 437 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 438 | buffer[offset++] = (op[1] >> 16) & 0xFF; 439 | buffer[offset++] = (op[1] >> 24) & 0xFF; 440 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 441 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 442 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 443 | break; 444 | } 445 | 446 | case I_OR_MRD: 447 | case I_AND_MRD: 448 | case I_SUB_MRD: 449 | case I_ADD_MRD: 450 | case I_MOV_MRD: { 451 | buffer[offset++] = i_data[id].opcode; 452 | buffer[offset++] = 0x05|(r_data[op[1]].encoding<<3); 453 | buffer[offset++] = op[1] & 0xFF; 454 | buffer[offset++] = (op[1] >> 8 ) & 0xFF; 455 | buffer[offset++] = (op[1] >> 16) & 0xFF; 456 | buffer[offset++] = (op[1] >> 24) & 0xFF; 457 | debug_putstr(": %2x %2x %2x %2x %2x %2x", 458 | buffer[offset-6], buffer[offset-5], buffer[offset-4], 459 | buffer[offset-3], buffer[offset-2], buffer[offset-1]); 460 | break; 461 | } 462 | 463 | case I_OR_RRMD: 464 | case I_AND_RRMD: 465 | case I_SUB_RRMD: 466 | case I_ADD_RRMD: 467 | case I_CMP_RRMD: 468 | case I_MOV_RRMD: { 469 | buffer[offset++] = i_data[id].opcode; 470 | buffer[offset++] = (r_data[op[0]].encoding<<3)|r_data[op[1]].encoding; 471 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 472 | break; 473 | } 474 | 475 | case I_OR_RMRD: 476 | case I_AND_RMRD: 477 | case I_SUB_RMRD: 478 | case I_ADD_RMRD: 479 | case I_MOV_RMRD: { 480 | buffer[offset++] = i_data[id].opcode; 481 | buffer[offset++] = (r_data[op[1]].encoding<<3)|r_data[op[0]].encoding; 482 | debug_putstr(": %2x %2x", buffer[offset-2], buffer[offset-1]); 483 | break; 484 | } 485 | 486 | default: { 487 | debug_putstr(": Instruction not found\n"); 488 | return ERROR_NOT_FOUND; 489 | } 490 | 491 | }; 492 | 493 | return offset; 494 | } 495 | 496 | // Encode and write data to buffer 497 | uint encode_data(uint8_t *buffer, uint offset, uint type, uint value) 498 | { 499 | switch(type) { 500 | 501 | case S_DATAD: { 502 | buffer[offset++] = value & 0xFF; 503 | buffer[offset++] = (value >> 8 ) & 0xFF; 504 | buffer[offset++] = (value >> 16) & 0xFF; 505 | buffer[offset++] = (value >> 24) & 0xFF; 506 | break; 507 | } 508 | 509 | case S_DATAW: { 510 | buffer[offset++] = value & 0xFF; 511 | buffer[offset++] = value >> 8; 512 | break; 513 | } 514 | 515 | case S_DATAB: { 516 | buffer[offset++] = value & 0xFF; 517 | break; 518 | } 519 | 520 | default: { 521 | debug_putstr(": Unknown data type\n"); 522 | break; 523 | } 524 | 525 | }; 526 | return offset; 527 | } 528 | 529 | // Given a file and offset, returns a file line and offset to next line start 530 | static uint read_line(char *buff, uint buff_size, char *file, uint offset) 531 | { 532 | // Clear buffer and read 533 | memset(buff, 0, buff_size); 534 | uint readed = read_file(buff, file, offset, buff_size); 535 | 536 | // Return on error 537 | if(readed >= ERROR_ANY) { 538 | return readed; 539 | } 540 | 541 | // Return EOF in case 542 | if(readed == 0) { 543 | debug_putstr("EOF\n"); 544 | return EOF; 545 | } 546 | 547 | // Clamp buff to a single line 548 | for(uint i=0; imnemonic, i_data[id].mnemonic) && 669 | ci->nops==i_data[id].nops) { 670 | 671 | // Assume match 672 | uint match = 1; 673 | for(uint i=0; inops; i++) { 674 | 675 | // Unless some arg is not the same type 676 | if(ci->op_type[i] != i_data[id].op_type[i]) { 677 | match = 0; 678 | break; 679 | } 680 | 681 | // Or a non matching specific value is required 682 | if(i_data[id].op_value[i]!=0 && 683 | i_data[id].op_value[i]!=ci->op_value[i]) { 684 | match = 0; 685 | break; 686 | } 687 | } 688 | 689 | // Not match, continue search 690 | if(!match) { 691 | continue; 692 | } 693 | 694 | // Match: return id 695 | return id; 696 | } 697 | } 698 | return ERROR_NOT_FOUND; 699 | } 700 | 701 | // Program entry point 702 | int main(int argc, char *argv[]) 703 | { 704 | // Check args 705 | if(argc != 2) { 706 | putstr("usage: %s \n", argv[0]); 707 | return 0; 708 | } 709 | 710 | // Compute output file name 711 | char ofile[14] = {0}; 712 | strncpy(ofile, argv[1], sizeof(ofile)); 713 | uint dot = strchr(ofile, '.'); 714 | if(dot) { 715 | ofile[dot-1] = 0; 716 | } 717 | ofile[sizeof(ofile)-strlen(".bin")-2] = 0; 718 | strncat(ofile, ".bin", sizeof(ofile)); 719 | 720 | // Output buffer and offset 721 | uint8_t obuff[1024] = {0}; 722 | uint ooffset = 0; 723 | // Clear output buffer 724 | memset(obuff, 0, sizeof(obuff)); 725 | 726 | // Clear symbols table 727 | memset(s_table, 0, sizeof(s_table)); 728 | 729 | // Clear references table 730 | memset(s_ref, 0, sizeof(s_ref)); 731 | 732 | // Input file buffer and offset 733 | char fbuff[2048] = {0}; 734 | uint foffset = 0; 735 | // Process input file line by line 736 | uint fline = 1; 737 | while((foffset=read_line(fbuff, sizeof(fbuff), argv[1], foffset))!=EOF) { 738 | // Exit on read error 739 | if(foffset >= ERROR_ANY) { 740 | putstr("Error reading input file\n"); 741 | debug_putstr("Error (%x) reading input file (%s)\n", foffset, argv[0]); 742 | ooffset = 0; 743 | break; 744 | } 745 | 746 | // Tokenize line 747 | char *tokv[32] = {NULL}; 748 | const uint tokc = 749 | tokenize_line(fbuff, sizeof(fbuff), tokv, sizeof(tokv)/sizeof(char*)); 750 | 751 | // Check special directives 752 | if(tokc==2 && !strcmp(tokv[0], "ORG")) { 753 | cg_origin = stou(tokv[1]); 754 | debug_putstr("origin = %x\n", cg_origin); 755 | } 756 | 757 | // Check labels 758 | else if(tokc==1 && tokv[0][strlen(tokv[0])-1] == ':') { 759 | tokv[0][strlen(tokv[0])-1] = 0; 760 | const uint symbol = find_or_add_symbol(tokv[0]); 761 | s_table[symbol].value = ooffset; 762 | s_table[symbol].type = S_LABEL; 763 | 764 | debug_putstr("label %s = ORG+%x\n", 765 | s_table[symbol].name, s_table[symbol].value); 766 | } 767 | 768 | // Check memory addresses 769 | else if(tokc>=3 && !strcmp(tokv[1], "dd")) { 770 | const uint symbol = find_or_add_symbol(tokv[0]); 771 | s_table[symbol].value = ooffset; 772 | s_table[symbol].type = S_DATAD; 773 | 774 | debug_putstr("dword %s = ORG+%x : ", 775 | s_table[symbol].name, s_table[symbol].value); 776 | 777 | for(uint n=2; n=3 && !strcmp(tokv[1], "dw")) { 785 | const uint symbol = find_or_add_symbol(tokv[0]); 786 | s_table[symbol].value = ooffset; 787 | s_table[symbol].type = S_DATAW; 788 | 789 | debug_putstr("word %s = ORG+%x : ", 790 | s_table[symbol].name, s_table[symbol].value); 791 | 792 | for(uint n=2; n=3 && !strcmp(tokv[1], "db")) { 800 | const uint symbol = find_or_add_symbol(tokv[0]); 801 | s_table[symbol].value = ooffset; 802 | s_table[symbol].type = S_DATAB; 803 | 804 | debug_putstr("byte %s = ORG+%x : ", 805 | s_table[symbol].name, s_table[symbol].value); 806 | 807 | for(uint n=2; n