I am making a simple OS, and I want to read letters from disk in C kernel and display them on screen, I am using x86 SeaBIOS on Qemu emulator on M1 Mac. I save data using ata_write(1, 1, data)
and read using ata_read(1, 1, buffer)
. When I write loaded characters to video memory, blank characters are displayed, even though I am setting color bit to 0x0F
(white on black).
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int size_t;
typedef unsigned int uint32_t;
typedef char int8_t;
#define VGA_ADDRESS 0xB8000
#define ATA_PRIMARY_IO 0x1F0
#define ATA_PRIMARY_CONTROL 0x3F6
#define ATA_SECONDARY_IO 0x170
#define ATA_SECONDARY_CONTROL 0x376
#define ATA_REG_DATA 0x00
#define ATA_REG_ERROR 0x01
#define ATA_REG_SECCOUNT0 0x02
#define ATA_REG_LBA0 0x03
#define ATA_REG_LBA1 0x04
#define ATA_REG_LBA2 0x05
#define ATA_REG_HDDEVSEL 0x06
#define ATA_REG_COMMAND 0x07
#define ATA_REG_STATUS 0x07
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_WRITE_PIO 0x30
#define FS_MAX_FILES 16
#define FS_FILENAME_LEN 32
#define FS_SECTOR_SIZE 512
static inline void outb(uint16_t port, uint8_t val) {
asm volatile("outb %0, %1" : : "a"(val), "Nd"(port));
}
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
asm volatile("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
static inline void insl(uint16_t port, void* addr, uint32_t cnt) {
asm volatile("cld; rep insl" : "=D"(addr), "=c"(cnt) : "d"(port), "0"(addr), "1"(cnt) : "memory", "cc");
}
static inline void outsl(uint16_t port, const void* addr, uint32_t cnt) {
asm volatile("cld; rep outsl" : "=S"(addr), "=c"(cnt) : "d"(port), "0"(addr), "1"(cnt) : "memory", "cc");
}
void ata_read(uint32_t lba, uint8_t sector_count, void* buffer) {
outb(ATA_PRIMARY_IO + ATA_REG_HDDEVSEL, 0xE0 | ((lba >> 24) & 0x0F));
outb(ATA_PRIMARY_IO + ATA_REG_SECCOUNT0, sector_count);
outb(ATA_PRIMARY_IO + ATA_REG_LBA0, (uint8_t) lba);
outb(ATA_PRIMARY_IO + ATA_REG_LBA1, (uint8_t) (lba >> 8));
outb(ATA_PRIMARY_IO + ATA_REG_LBA2, (uint8_t) (lba >> 16));
outb(ATA_PRIMARY_IO + ATA_REG_COMMAND, ATA_CMD_READ_PIO);
for (int i = 0; i < 4; i++) inb(ATA_PRIMARY_IO + ATA_REG_STATUS);
insl(ATA_PRIMARY_IO + ATA_REG_DATA, buffer, 128);
}
void ata_write(uint32_t lba, uint8_t sector_count, const void* buffer) {
outb(ATA_PRIMARY_IO + ATA_REG_HDDEVSEL, 0xE0 | ((lba >> 24) & 0x0F));
outb(ATA_PRIMARY_IO + ATA_REG_SECCOUNT0, sector_count);
outb(ATA_PRIMARY_IO + ATA_REG_LBA0, (uint8_t) lba);
outb(ATA_PRIMARY_IO + ATA_REG_LBA1, (uint8_t) (lba >> 8));
outb(ATA_PRIMARY_IO + ATA_REG_LBA2, (uint8_t) (lba >> 16));
outb(ATA_PRIMARY_IO + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO);
for (int i = 0; i < 4; i++) inb(ATA_PRIMARY_IO + ATA_REG_STATUS);
outsl(ATA_PRIMARY_IO + ATA_REG_DATA, buffer, 128);
}
typedef struct {
char name[FS_FILENAME_LEN];
uint32_t start_sector;
uint32_t size;
} file_t;
static file_t fs_files[FS_MAX_FILES];
static uint32_t fs_file_count = 0;
int main(){
char *v = (char *)VGA_ADDRESS;
const char* data = "Hello, Disk File System!";
ata_write(1, 1, data); // Write to sector 1
char buffer[512];
ata_read(1, 1, buffer); // Read from sector 1
// The letters "H", "e", "l" should be read from disk and displayed
*(v + 2 * 2) = buffer[0];
*(v + 2 * 2 + 1) = 0x0F;
*(v + 3 * 2) = buffer[1];
*(v + 3 * 2 + 1) = 0x0F;
*(v + 4 * 2) = buffer[2];
*(v + 4 * 2 + 1) = 0x0F;
}
Here is my assembly code for setting up disk operations.
; CHS adressing
; cylinder, head, sector
; load 'dh' sectors from drive 'dl' into ES:BX
disk_load:
pusha
; reading from disk requires setting specific values in all registers
; so we will overwrite our input parameters from 'dx'. Let's save it
; to the stack for later use.
push dx
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
; 0x01 is our boot sector, 0x02 is the first 'available' sector
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
; [es:bx] <- pointer to buffer where the data will be stored
; caller sets it up for us, and it is actually the standard location for int 13h
int 0x13 ; BIOS interrupt
jc disk_error ; if error (stored in the carry bit)
pop dx
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
jne sectors_error
popa
ret
disk_error:
jmp disk_loop
sectors_error:
jmp disk_loop
disk_loop:
jmp $
Does anybody know what might be the problem?
Thanks!