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 <mem/alloc.h>
7#include <mem/pmm.h>
8#include <mem/vmm.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <string.h>
12#include <time/spin_sleep.h>
13
14// TODO: Hand this off to IDE if the GHC bit 31 is OFF
15// It won't be AHCI - Sometimes we are in IDE emul mode
16
17/* TODO: file is very messy. clean this up you goober >:C */
18
19static void setup_port_slots(struct ahci_device *dev, uint32_t port_id) {
20 struct ahci_full_port *port = &dev->regs[port_id];
21 for (uint64_t slot = 0; slot < 32; slot++) {
22 uint64_t cmdtbl_phys = pmm_alloc_page();
23
24 void *cmdtbl_virt = mmio_map(phys: cmdtbl_phys, PAGE_SIZE);
25 memset(cmdtbl_virt, 0, PAGE_SIZE);
26
27 struct ahci_cmd_header *cmd_header =
28 (port->cmd_list_base + slot * sizeof(struct ahci_cmd_header));
29
30 cmd_header->ctba = (uint32_t) (cmdtbl_phys & 0xFFFFFFFF);
31 cmd_header->ctbau = (uint32_t) (cmdtbl_phys >> 32);
32 cmd_header->prdtl = 1;
33 port->cmd_tables[slot] = cmdtbl_virt;
34 port->cmd_hdrs[slot] = cmd_header;
35 }
36}
37
38/* Stop the port so it doesn't start overwriting potential kernel text */
39static void ahci_port_quiesce(struct ahci_port *port) {
40 mmio_write_32(address: &port->cmd, value: mmio_read_32(address: &port->cmd) & ~AHCI_CMD_ST);
41 mmio_spin_wait(reg: &port->cmd, AHCI_CMD_CR, AHCI_CMD_TIMEOUT_MS);
42
43 mmio_write_32(address: &port->cmd, value: mmio_read_32(address: &port->cmd) & ~AHCI_CMD_FRE);
44 mmio_spin_wait(reg: &port->cmd, AHCI_CMD_FR, AHCI_CMD_TIMEOUT_MS);
45
46 mmio_write_32(address: &port->clb, value: 0);
47 mmio_write_32(address: &port->clbu, value: 0);
48 mmio_write_32(address: &port->fb, value: 0);
49 mmio_write_32(address: &port->fbu, value: 0);
50}
51
52static void allocate_port(struct ahci_device *dev, struct ahci_port *port,
53 uint32_t port_num) {
54 uint64_t cmdlist_phys = pmm_alloc_page();
55 uint64_t fis_phys = pmm_alloc_page();
56 void *cmdlist = mmio_map(phys: cmdlist_phys, PAGE_SIZE);
57 void *fis = mmio_map(phys: fis_phys, PAGE_SIZE);
58 memset(cmdlist, 0, PAGE_SIZE);
59 memset(fis, 0, PAGE_SIZE);
60
61 mmio_write_32(address: &port->clb, value: cmdlist_phys & 0xFFFFFFFFUL);
62 mmio_write_32(address: &port->clbu, value: cmdlist_phys >> 32);
63 mmio_write_32(address: &port->fb, value: fis_phys & 0xFFFFFFFFUL);
64 mmio_write_32(address: &port->fbu, value: fis_phys >> 32);
65 struct ahci_cmd_table **arr =
66 kmalloc(sizeof(struct ahci_cmd_table *) * 32, ALLOC_FLAGS_ZERO);
67 struct ahci_cmd_header **hdr =
68 kmalloc(sizeof(struct ahci_cmd_header *) * 32, ALLOC_FLAGS_ZERO);
69
70 if (!arr || !hdr)
71 panic("Could not allocate space for AHCI commands");
72
73 struct ahci_full_port p = {.port = port,
74 .fis = fis,
75 .cmd_list_base = cmdlist,
76 .cmd_tables = arr,
77 .cmd_hdrs = hdr};
78 dev->regs[port_num] = p;
79}
80
81static struct ahci_disk *device_setup(struct ahci_device *dev,
82 struct ahci_controller *ctrl,
83 uint32_t *disk_count) {
84 uint32_t pi = mmio_read_32(address: &ctrl->pi);
85
86 mmio_write_32(address: &dev->ctrl->ghc, value: 1 << 1);
87 uint32_t total_disks = 0;
88
89 for (uint32_t i = 0; i < 32; i++) {
90 if (!(pi & (1U << i)))
91 continue;
92
93 struct ahci_port *port = ahci_get_port(dev, n: i);
94
95 ahci_port_quiesce(port);
96
97 uint32_t ssts = mmio_read_32(address: &port->ssts);
98 uint32_t det = ssts & 0x0F;
99 uint32_t ipm = (ssts >> 8) & 0x0F;
100 if (!(det == AHCI_DET_PRESENT && ipm == AHCI_IPM_ACTIVE))
101 continue;
102
103 uint32_t sig = mmio_read_32(address: &port->sig);
104 ahci_log(LOG_INFO, "Controller port %u has signature %p", i, sig);
105
106 if (sig == 0xFFFFFFFF)
107 continue;
108
109 if (sig != 0x00000101) {
110 ahci_log(LOG_WARN, "Controller port %u is not an HDD, skipping...",
111 i, sig);
112 continue;
113 }
114
115 dev->port_count++;
116 total_disks++;
117 }
118
119 if (!total_disks)
120 return NULL;
121
122 *disk_count = total_disks;
123
124 struct ahci_disk *disks =
125 kmalloc(sizeof(struct ahci_disk) * total_disks, ALLOC_FLAGS_ZERO);
126 if (!disks)
127 panic("Could not allocate space for AHCI disks");
128
129 uint32_t disks_ind = 0;
130
131 for (uint32_t i = 0; i < 32; i++) {
132 if (!(pi & (1U << i)))
133 continue;
134
135 struct ahci_port *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 *ctrl,
168 uint32_t *d_cnt) {
169 bool s64a = mmio_read_32(address: &ctrl->cap) & (1U << 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 =
182 kmalloc(sizeof(struct ahci_device), ALLOC_FLAGS_ZERO);
183 if (!dev)
184 panic("Could not allocate space for AHCI device setup");
185
186 dev->ctrl = ctrl;
187 dev->irq_num = irq_alloc_entry();
188
189 uint32_t disk_count = 0;
190 struct ahci_disk *d = device_setup(dev, ctrl, disk_count: &disk_count);
191 *d_cnt = disk_count;
192 ahci_log(LOG_INFO,
193 "Device initialized successfully, %u usable port(s) present",
194 disk_count);
195
196 return d;
197}
198