1#include <asm.h>
2#include <console/printf.h>
3#include <drivers/ahci.h>
4#include <drivers/mmio.h>
5#include <irq/idt.h>
6#include <math/bit.h>
7#include <mem/alloc.h>
8#include <mem/pmm.h>
9#include <mem/vmm.h>
10#include <stdbool.h>
11#include <stdint.h>
12#include <string.h>
13#include <time/spin_sleep.h>
14
15// TODO: Hand this off to IDE if the GHC bit 31 is OFF
16// It won't be AHCI - Sometimes we are in IDE emul mode
17
18/* TODO: file is very messy. clean this up you goober >:C */
19
20static void setup_port_slots(struct ahci_device *dev, uint32_t port_id) {
21 struct ahci_full_port *port = &dev->regs[port_id];
22 for (uint64_t slot = 0; slot < 32; slot++) {
23 uint64_t cmdtbl_phys = pmm_alloc_page();
24
25 void *cmdtbl_virt = mmio_map_dma(phys: cmdtbl_phys, PAGE_SIZE);
26 memset(cmdtbl_virt, 0, PAGE_SIZE);
27
28 struct ahci_cmd_header *cmd_header =
29 (port->cmd_list_base + slot * sizeof(struct ahci_cmd_header));
30
31 cmd_header->ctba = (uint32_t) (cmdtbl_phys & 0xFFFFFFFF);
32 cmd_header->ctbau = (uint32_t) (cmdtbl_phys >> 32);
33 cmd_header->prdtl = 1;
34 port->cmd_tables[slot] = cmdtbl_virt;
35 port->cmd_hdrs[slot] = cmd_header;
36 }
37}
38
39/* Stop the port so it doesn't start overwriting potential kernel text */
40static void ahci_port_quiesce(struct ahci_port cc_mem_io *port) {
41 mmio_write_32(address: &port->cmd, value: mmio_read_32(address: &port->cmd) & ~AHCI_CMD_ST);
42 mmio_spin_wait(reg: &port->cmd, AHCI_CMD_CR, AHCI_CMD_TIMEOUT_MS);
43
44 mmio_write_32(address: &port->cmd, value: mmio_read_32(address: &port->cmd) & ~AHCI_CMD_FRE);
45 mmio_spin_wait(reg: &port->cmd, AHCI_CMD_FR, AHCI_CMD_TIMEOUT_MS);
46
47 mmio_write_32(address: &port->clb, value: 0);
48 mmio_write_32(address: &port->clbu, value: 0);
49 mmio_write_32(address: &port->fb, value: 0);
50 mmio_write_32(address: &port->fbu, value: 0);
51}
52
53static void allocate_port(struct ahci_device *dev,
54 struct ahci_port cc_mem_io *port, uint32_t port_num) {
55 uint64_t cmdlist_phys = pmm_alloc_page();
56 uint64_t fis_phys = pmm_alloc_page();
57 void *cmdlist = mmio_map_dma(phys: cmdlist_phys, PAGE_SIZE);
58 void *fis = mmio_map_dma(phys: fis_phys, PAGE_SIZE);
59 memset(cmdlist, 0, PAGE_SIZE);
60 memset(fis, 0, PAGE_SIZE);
61
62 mmio_write_32(address: &port->clb, value: cmdlist_phys & 0xFFFFFFFFUL);
63 mmio_write_32(address: &port->clbu, value: cmdlist_phys >> 32);
64 mmio_write_32(address: &port->fb, value: fis_phys & 0xFFFFFFFFUL);
65 mmio_write_32(address: &port->fbu, value: fis_phys >> 32);
66 struct ahci_cmd_table **arr = vnews(arr, 32, ALLOC_ZERO);
67 struct ahci_cmd_header **hdr = vnews(hdr, 32, ALLOC_ZERO);
68
69 if (!arr || !hdr)
70 panic(OUT_OF_MEMORY, "AHCI commands");
71
72 struct ahci_full_port p = {.port = port,
73 .fis = fis,
74 .cmd_list_base = cmdlist,
75 .cmd_tables = arr,
76 .cmd_hdrs = hdr};
77 dev->regs[port_num] = p;
78}
79
80static struct ahci_disk *device_setup(struct ahci_device *dev,
81 struct ahci_controller cc_mem_io *ctrl,
82 uint32_t *disk_count) {
83 uint32_t pi = mmio_read_32(address: &ctrl->pi);
84
85 mmio_write_32(address: &dev->ctrl->ghc, BIT(1));
86 uint32_t total_disks = 0;
87
88 for (uint32_t i = 0; i < 32; i++) {
89 if (!BIT_TEST(pi, i))
90 continue;
91
92 struct ahci_port cc_mem_io *port = ahci_get_port(dev, n: i);
93
94 ahci_port_quiesce(port);
95
96 uint32_t ssts = mmio_read_32(address: &port->ssts);
97 uint32_t det = ssts & 0x0F;
98 uint32_t ipm = (ssts >> 8) & 0x0F;
99 if (!(det == AHCI_DET_PRESENT && ipm == AHCI_IPM_ACTIVE))
100 continue;
101
102 uint32_t sig = mmio_read_32(address: &port->sig);
103 ahci_log(LOG_INFO, "Controller port %u has signature 0x%08x", i, sig);
104
105 if (sig == 0xFFFFFFFF)
106 continue;
107
108 if (sig != 0x00000101) {
109 ahci_log(
110 LOG_WARN,
111 "Controller port %u is not an HDD (sig 0x%08x), skipping...", i,
112 sig);
113 continue;
114 }
115
116 dev->port_count++;
117 total_disks++;
118 }
119
120 if (!total_disks)
121 return NULL;
122
123 *disk_count = total_disks;
124
125 struct ahci_disk *disks = vnews(disks, total_disks, ALLOC_ZERO);
126 if (!disks)
127 panic(OUT_OF_MEMORY, "AHCI disks");
128
129 uint32_t disks_ind = 0;
130
131 for (uint32_t i = 0; i < 32; i++) {
132 if (!BIT_TEST(pi, i))
133 continue;
134
135 struct ahci_port cc_mem_io *port = ahci_get_port(dev, n: i);
136 uint32_t ssts = mmio_read_32(address: &port->ssts);
137
138 if ((ssts & 0x0F) == AHCI_DET_PRESENT &&
139 ((ssts >> 8) & 0x0F) == AHCI_IPM_ACTIVE) {
140 uint32_t sig = mmio_read_32(address: &port->sig);
141
142 if (sig != 0x00000101)
143 continue;
144
145 disks[disks_ind].port = i;
146 disks[disks_ind].device = dev;
147
148 mmio_write_32(address: &port->is, value: 0xFFFFFFFF);
149 mmio_write_32(address: &port->ie, value: 0xFFFFFFFF);
150
151 ahci_port_quiesce(port);
152
153 /* FRE may only be set once PxFB points at our memory */
154 allocate_port(dev, port, port_num: i);
155 mmio_write_32(address: &port->cmd, value: mmio_read_32(address: &port->cmd) | AHCI_CMD_FRE);
156
157 /* ST is set after the port slots are setup */
158 setup_port_slots(dev, port_id: i);
159 mmio_write_32(address: &port->cmd, value: mmio_read_32(address: &port->cmd) | AHCI_CMD_ST);
160
161 ahci_log(LOG_INFO, "Port %u slots set up", i);
162 }
163 }
164 return disks;
165}
166
167struct ahci_disk *ahci_setup_controller(struct ahci_controller cc_mem_io *ctrl,
168 uint32_t *d_cnt) {
169 bool s64a = BIT_TEST(mmio_read_32(&ctrl->cap), 31);
170 if (!s64a) {
171 ahci_log(LOG_WARN, "controller does not support 64-bit addressing\n");
172 return NULL;
173 }
174
175 mmio_write_32(address: &ctrl->ghc, AHCI_GHC_HR);
176
177 mmio_spin_wait(reg: &ctrl->ghc, AHCI_GHC_HR, AHCI_CMD_TIMEOUT_MS);
178
179 mmio_write_32(address: &ctrl->ghc, value: mmio_read_32(address: &ctrl->ghc) | AHCI_GHC_AE);
180
181 struct ahci_device *dev = vnew(dev, ALLOC_ZERO);
182 if (!dev)
183 panic(OUT_OF_MEMORY, "AHCI device setup");
184
185 dev->ctrl = ctrl;
186 dev->irq_num = irq_alloc_entry();
187
188 uint32_t disk_count = 0;
189 struct ahci_disk *d = device_setup(dev, ctrl, disk_count: &disk_count);
190 *d_cnt = disk_count;
191 ahci_log(LOG_INFO,
192 "Device initialized successfully, %u usable port(s) present",
193 disk_count);
194
195 return d;
196}
197