aboutsummaryrefslogtreecommitdiff
path: root/src/kernel/fs/ata.c
blob: 2e952a1e1adc6340b49a8094cd01e8275628b05f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <fs/ata.h>
#include <io/io.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <system.h>

static u16 sel_base_port = 0;
static u8 sel_master_or_slave = 0;

static u32 max_sector;

static u8 read_stat(u16 base)
{
	inb(base + COM_STAT);
	inb(base + COM_STAT);
	inb(base + COM_STAT);
	inb(base + COM_STAT);

	return inb(base + COM_STAT);
}

static void check_drive(u16 base, u8 master_or_slave)
{
	if (sel_base_port != 0)
		return;

	outb(base + DRIVE_SELECT, master_or_slave);

	outb(base + SECTOR_COUNT, 0);
	outb(base + LBA_LOW, 0);
	outb(base + LBA_MID, 0);
	outb(base + LBA_HIGH, 0);

	outb(base + COM_STAT, IDENTIFY);
	u8 stat = read_stat(base);

	if (stat == 0)
		return;

	while ((stat & BSY) != 0)
		stat = read_stat(base);

	while ((stat & DRQ) == 0 && (stat & ERR) == 0)
		stat = read_stat(base);

	if ((stat & ERR) != 0)
		return;

	u16 drive_data[256];
	for (u32 i = 0; i < 256; i++)
		drive_data[i] = inw(base + DATA);

	max_sector = drive_data[MAX_28LBA_SECTORS] | drive_data[MAX_28LBA_SECTORS + 1] << 16;

	sel_base_port = base;
	sel_master_or_slave = master_or_slave;
}

void ata_init()
{
	u8 pri_status = inb(PRIMARY_BASE + COM_STAT);
	u8 sec_status = inb(SECONDARY_BASE + COM_STAT);
	bool primary_floating = false;
	bool secondary_floating = false;
	if (pri_status == 0xFF)
		primary_floating = true;
	if (sec_status == 0xFF)
		secondary_floating = true;

	if (primary_floating && secondary_floating) {
		log("No drives attached! What's going on?");
		return;
	}

	check_drive(PRIMARY_BASE, SEL_MASTER);
	check_drive(PRIMARY_BASE, SEL_SLAVE);
	check_drive(SECONDARY_BASE, SEL_MASTER);
	check_drive(SECONDARY_BASE, SEL_SLAVE);

	if (sel_base_port == 0)
		log("No drives attached! What's going on?");
	else {
		log("Found drive: Selecting %s on the %s bus",
		    sel_master_or_slave == SEL_MASTER ? "master" : "slave",
		    sel_base_port == PRIMARY_BASE ? "primary" : "secondary");
		log("Max LBA value is %d", max_sector);
	}
}

static void poll()
{
	u8 stat;

	do
		stat = read_stat(sel_base_port);
	while ((stat & BSY) != 0);
}

void read_abs_sectors(u32 lba, u8 sector_count, u16 buf[])
{
	assert(lba >> LBA_BITS == 0);

	outb(sel_base_port + DRIVE_SELECT, (lba >> (LBA_BITS - 4)) | sel_master_or_slave | 1 << 6);
	outb(sel_base_port + SECTOR_COUNT, sector_count);

	outb(sel_base_port + LBA_LOW, lba & 0xFF);
	outb(sel_base_port + LBA_MID, (lba >> 8) & 0xFF);
	outb(sel_base_port + LBA_HIGH, (lba >> 16) & 0xFF);

	outb(sel_base_port + COM_STAT, READ_SECTORS);

	u32 i = 0;
	for (; sector_count > 0; sector_count--) {
		poll();

		asm("rep insw" ::"c"(SECTOR_SIZE / 2), "d"(sel_base_port + DATA), "D"(buf + i));
		i += SECTOR_SIZE / 2;
	}
}