2 * Acess2 Kernel - AHCI Driver
3 * - By John Hodge (thePowersGang)
17 #define MAX_CONTROLLERS 4
20 int AHCI_Install(char **Arguments);
21 int AHCI_Cleanup(void);
23 int AHCI_InitSys(tAHCI_Ctrlr *Ctrlr);
26 MODULE_DEFINE(0, VERSION, AHCI, AHCI_Install, AHCI_Cleanup, "LVM", NULL);
27 tAHCI_Ctrlr gaAHCI_Controllers[MAX_CONTROLLERS];
30 int AHCI_Install(char **Arguments)
33 LOG("offsetof(struct sAHCI_MemSpace, Ports) = %x", offsetof(struct sAHCI_MemSpace, Ports));
34 ASSERT( offsetof(struct sAHCI_MemSpace, Ports) == 0x100 );
36 // 0106XX = Standard, 010400 = RAID
39 while( (id = PCI_GetDeviceByClass(0x010601, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS )
41 tAHCI_Ctrlr *ctrlr = &gaAHCI_Controllers[i];
42 ctrlr->PMemBase = PCI_GetBAR(id, 5);
44 if( !ctrlr->PMemBase )
46 // ctrlr->PMemBase = PCI_AllocateBAR(id, 5);
47 // TODO: Allocate space
51 if( (ctrlr->PMemBase & 0x1FFF) ) {
52 Log_Warning("AHCI", "Controller %i [PCI %i] is invalid (BAR5=%P)",
53 i, id, ctrlr->PMemBase);
57 ctrlr->IRQ = PCI_GetIRQ(id);
59 // Prepare MMIO (two pages according to the spec)
60 ctrlr->MMIO = (void*)MM_MapHWPages(ctrlr->PMemBase, 2);
62 LOG("%i [%i]: %P/IRQ%i mapped to %p",
63 i, id, ctrlr->PMemBase, ctrlr->IRQ, ctrlr->MMIO);
65 LOG(" CAP = %x, PI = %x, VS = %x",
66 ctrlr->MMIO->CAP, ctrlr->MMIO->PI, ctrlr->MMIO->VS);
68 if( (rv = AHCI_InitSys(ctrlr)) ) {
69 // Clean up controller's allocations
70 // TODO: Should an init error cause module unload?
77 Log_Notice("AHCI", "Only up to %i controllers are supported", MAX_CONTROLLERS);
83 int AHCI_Cleanup(void)
88 static inline void *AHCI_AllocPage(tAHCI_Ctrlr *Ctrlr, const char *AllocName)
92 if( Ctrlr->Supports64Bit )
93 ret = (void*)MM_AllocDMA(1, 64, NULL);
96 ret = (void*)MM_AllocDMA(1, 32, NULL);
98 Log_Error("AHCI", "Unable to allocate page for '%s'", AllocName);
104 int AHCI_InitSys(tAHCI_Ctrlr *Ctrlr)
107 Ctrlr->MMIO->GHC |= AHCI_GHC_AE;
108 // 2. Enumerate ports using PI
109 tTime basetime = now();
110 for( int i = 0; i < 32; i ++ )
112 if( !(Ctrlr->MMIO->PI & (1 << i)) )
117 volatile struct s_port *port = &Ctrlr->MMIO->Ports[i];
118 // 3. (for each port) Ensure that port is not running
119 // - if PxCMD.(ST|CR|FRE|FR) all are clear, port is idle
120 if( (port->PxCMD & (AHCI_PxCMD_ST|AHCI_PxCMD_CR|AHCI_PxCMD_FRE|AHCI_PxCMD_FR)) == 0 )
122 // - Idle is set by clearing PxCMD.ST and waiting for .CR to clear (timeout 500ms)
123 // - AND .FRE = 0, checking .FR (timeout 500ms again)
126 // > On timeout, port/HBA reset
128 Ctrlr->Ports = malloc( Ctrlr->PortCount * sizeof(*Ctrlr->Ports) );
129 if( !Ctrlr->Ports ) {
130 return MODULE_ERR_MALLOC;
132 // - Process timeouts after all ports have been poked, saves time
133 for( int i = 0, idx = 0; i < 32; i ++ )
135 if( !(Ctrlr->MMIO->PI & (1 << i)) )
137 volatile struct s_port *port = &Ctrlr->MMIO->Ports[i];
138 Ctrlr->Ports[idx].Idx = i;
139 Ctrlr->Ports[idx].MMIO = port;
142 while( (port->PxCMD & (AHCI_PxCMD_CR|AHCI_PxCMD_FR)) && now()-basetime < 500 )
145 if( (port->PxCMD & (AHCI_PxCMD_CR|AHCI_PxCMD_FR)) )
147 Log_Error("AHCI", "Port %i did not return to idle", i);
150 // 4. Read CAP.NCS to get number of command slots
151 Ctrlr->NCS = (Ctrlr->MMIO->CAP & AHCI_CAP_NCS) >> AHCI_CAP_NCS_ofs;
152 // 5. Allocate PxCLB and PxFB for each port (setting PxCMD.FRE once done)
153 struct sAHCI_RcvdFIS *fis_vpage = NULL;
154 struct sAHCI_CmdHdr *cl_vpage = NULL;
155 for( int i = 0; i < Ctrlr->PortCount; i ++ )
157 // CLB First (1 KB alignemnt)
158 if( ((tVAddr)cl_vpage & 0xFFF) == 0 ) {
159 cl_vpage = AHCI_AllocPage(Ctrlr, "CLB");
161 return MODULE_ERR_MALLOC;
163 Ctrlr->Ports[i].CmdList = cl_vpage;
164 cl_vpage += 1024/sizeof(*cl_vpage);
166 tPAddr cl_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].CmdList);
167 Ctrlr->Ports[i].MMIO->PxCLB = cl_paddr;
169 Ctrlr->Ports[i].MMIO->PxCLBU = cl_paddr >> 32;
173 // - If there is space for the FIS in the end of the 1K block, use it
174 if( Ctrlr->NCS <= (1024-256)/32 )
176 Ctrlr->Ports[i].RcvdFIS = (void*)(cl_vpage - 256/32);
180 if( ((tVAddr)fis_vpage & 0xFFF) == 0 ) {
181 fis_vpage = AHCI_AllocPage(Ctrlr, "FIS");
183 return MODULE_ERR_MALLOC;
185 Ctrlr->Ports[i].RcvdFIS = fis_vpage;
188 tPAddr fis_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].RcvdFIS);
189 Ctrlr->Ports[i].MMIO->PxFB = fis_paddr;
191 Ctrlr->Ports[i].MMIO->PxFBU = fis_paddr >> 32;
194 LOG("Port #%i: CLB=%p/%P, FB=%p/%P", i,
195 Ctrlr->Ports[i].CmdList, cl_paddr,
196 Ctrlr->Ports[i].RcvdFIS, fis_paddr);
198 // 6. Clear PxSERR with 1 to each implimented bit
199 // > Clear PxIS then IS.IPS before setting PxIE/GHC.IE
200 // 7. Set PxIE with desired interrupts and set GHC.IE
202 // Detect present ports using:
203 // > PxTFD.STS.BSY = 0, PxTFD.STS.DRQ = 0, and PxSSTS.DET = 3
204 for( int i = 0; i < Ctrlr->PortCount; i ++ )
206 if( Ctrlr->Ports[i].MMIO->PxTFD & (AHCI_PxTFD_STS_BSY|AHCI_PxTFD_STS_DRQ) )
208 if( (Ctrlr->Ports[i].MMIO->PxSSTS & AHCI_PxSSTS_DET) != 3 << AHCI_PxSSTS_DET_ofs )
211 LOG("Port #%i: Connection detected", i);
212 Ctrlr->Ports[i].bPresent = 1;
213 // TODO: Query volumes connected