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);
24 void AHCI_QueryDevice(tAHCI_Ctrlr *Ctrlr, int PortNum);
25 void AHCI_IRQHandler(int UNUSED(IRQ), void *Data);
26 void AHCI_int_IRQHandlerPort(tAHCI_Port *Port);
28 int AHCI_ReadSectors(void *Ptr, Uint64 Address, size_t Count, void *Buffer);
29 int AHCI_WriteSectors(void *Ptr, Uint64 Address, size_t Count, const void *Buffer);
31 int AHCI_SendLBA28Cmd(tAHCI_Port *Port, int bWrite,
32 Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data
34 int AHCI_SendLBA48Cmd(tAHCI_Port *Port, int bWrite,
35 Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data
37 int AHCI_DoFIS(tAHCI_Port *Port, int bWrite,
38 size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
39 size_t OutSize, void *OutData
41 int AHCI_WaitForInterrupt(tAHCI_Port *Port, unsigned int Timeout);
44 MODULE_DEFINE(0, VERSION, AHCI, AHCI_Install, AHCI_Cleanup, "LVM", NULL);
45 tAHCI_Ctrlr gaAHCI_Controllers[MAX_CONTROLLERS];
46 tLVM_VolType gAHCI_VolumeType = {
48 .Read = AHCI_ReadSectors,
49 .Write = AHCI_WriteSectors
53 int AHCI_Install(char **Arguments)
56 LOG("offsetof(struct sAHCI_MemSpace, Ports) = %x", offsetof(struct sAHCI_MemSpace, Ports));
57 ASSERT( offsetof(struct sAHCI_MemSpace, Ports) == 0x100 );
59 // 0106XX = Standard, 010400 = RAID
62 while( (id = PCI_GetDeviceByClass(0x010601, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS )
64 tAHCI_Ctrlr *ctrlr = &gaAHCI_Controllers[i];
65 ctrlr->PMemBase = PCI_GetBAR(id, 5);
67 if( !ctrlr->PMemBase )
69 // ctrlr->PMemBase = PCI_AllocateBAR(id, 5);
70 // TODO: Allocate space
74 if( (ctrlr->PMemBase & 0x1FFF) ) {
75 Log_Warning("AHCI", "Controller %i [PCI %i] is invalid (BAR5=%P)",
76 i, id, ctrlr->PMemBase);
80 ctrlr->IRQ = PCI_GetIRQ(id);
81 IRQ_AddHandler(ctrlr->IRQ, AHCI_IRQHandler, ctrlr);
83 // Prepare MMIO (two pages according to the spec)
84 ctrlr->MMIO = (void*)MM_MapHWPages(ctrlr->PMemBase, 2);
86 LOG("%i [%i]: %P/IRQ%i mapped to %p",
87 i, id, ctrlr->PMemBase, ctrlr->IRQ, ctrlr->MMIO);
89 LOG(" CAP = %x, PI = %x, VS = %x",
90 ctrlr->MMIO->CAP, ctrlr->MMIO->PI, ctrlr->MMIO->VS);
92 if( (rv = AHCI_InitSys(ctrlr)) ) {
93 // Clean up controller's allocations
94 // TODO: Should an init error cause module unload?
101 Log_Notice("AHCI", "Only up to %i controllers are supported", MAX_CONTROLLERS);
107 int AHCI_Cleanup(void)
112 static inline void *AHCI_AllocPage(tAHCI_Ctrlr *Ctrlr, const char *AllocName)
116 if( Ctrlr->Supports64Bit )
117 ret = (void*)MM_AllocDMA(1, 64, NULL);
120 ret = (void*)MM_AllocDMA(1, 32, NULL);
122 Log_Error("AHCI", "Unable to allocate page for '%s'", AllocName);
128 static inline void AHCI_int_SetAddr(tAHCI_Ctrlr *Ctrlr, volatile Uint32 *Addr, tPAddr PAddr)
132 if(Ctrlr->Supports64Bit)
133 Addr[1] = PAddr >> 32;
139 int AHCI_InitSys(tAHCI_Ctrlr *Ctrlr)
142 Ctrlr->MMIO->GHC |= AHCI_GHC_AE;
143 // 2. Enumerate ports using PI
144 tTime basetime = now();
145 for( int i = 0; i < 32; i ++ )
147 if( !(Ctrlr->MMIO->PI & (1 << i)) )
152 volatile struct s_port *port = &Ctrlr->MMIO->Ports[i];
153 // 3. (for each port) Ensure that port is not running
154 // - if PxCMD.(ST|CR|FRE|FR) all are clear, port is idle
155 if( (port->PxCMD & (AHCI_PxCMD_ST|AHCI_PxCMD_CR|AHCI_PxCMD_FRE|AHCI_PxCMD_FR)) == 0 )
157 // - Idle is set by clearing PxCMD.ST and waiting for .CR to clear (timeout 500ms)
158 // - AND .FRE = 0, checking .FR (timeout 500ms again)
161 // > On timeout, port/HBA reset
163 Ctrlr->Ports = malloc( Ctrlr->PortCount * sizeof(*Ctrlr->Ports) );
164 if( !Ctrlr->Ports ) {
165 return MODULE_ERR_MALLOC;
167 // - Process timeouts after all ports have been poked, saves time
168 for( int i = 0, idx = 0; i < 32; i ++ )
170 if( !(Ctrlr->MMIO->PI & (1 << i)) )
172 volatile struct s_port *port = &Ctrlr->MMIO->Ports[i];
173 Ctrlr->Ports[idx].Ctrlr = Ctrlr;
174 Ctrlr->Ports[idx].Idx = i;
175 Ctrlr->Ports[idx].MMIO = port;
178 while( (port->PxCMD & (AHCI_PxCMD_CR|AHCI_PxCMD_FR)) && now()-basetime < 500 )
181 if( (port->PxCMD & (AHCI_PxCMD_CR|AHCI_PxCMD_FR)) )
183 Log_Error("AHCI", "Port %i did not return to idle", i);
186 // 4. Read CAP.NCS to get number of command slots
187 Ctrlr->NCS = (Ctrlr->MMIO->CAP & AHCI_CAP_NCS) >> AHCI_CAP_NCS_ofs;
188 // 5. Allocate PxCLB and PxFB for each port (setting PxCMD.FRE once done)
189 struct sAHCI_RcvdFIS *fis_vpage = NULL;
190 struct sAHCI_CmdHdr *cl_vpage = NULL;
191 struct sAHCI_CmdTable *ct_vpage = NULL;
192 for( int i = 0; i < Ctrlr->PortCount; i ++ )
194 tAHCI_Port *port = &Ctrlr->Ports[i];
195 // CLB First (1 KB alignemnt)
196 if( ((tVAddr)cl_vpage & 0xFFF) == 0 ) {
197 cl_vpage = AHCI_AllocPage(Ctrlr, "CLB");
199 return MODULE_ERR_MALLOC;
201 port->CmdList = cl_vpage;
202 cl_vpage += 1024/sizeof(*cl_vpage);
204 tPAddr cl_paddr = MM_GetPhysAddr(port->CmdList);
205 AHCI_int_SetAddr(Ctrlr, &port->MMIO->PxCLB, cl_paddr);
208 // - If there is space for the FIS in the end of the 1K block, use it
209 if( Ctrlr->NCS <= (1024-256)/32 )
211 Ctrlr->Ports[i].RcvdFIS = (void*)(cl_vpage - 256/32);
215 if( ((tVAddr)fis_vpage & 0xFFF) == 0 ) {
216 fis_vpage = AHCI_AllocPage(Ctrlr, "FIS");
218 return MODULE_ERR_MALLOC;
220 Ctrlr->Ports[i].RcvdFIS = fis_vpage;
223 tPAddr fis_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].RcvdFIS);
224 AHCI_int_SetAddr(Ctrlr, &port->MMIO->PxFB, fis_paddr);
227 for( int j = 0; j < Ctrlr->NCS; j ++ )
229 if( ((tVAddr)ct_vpage & 0xFFF) == 0 ) {
230 ct_vpage = AHCI_AllocPage(Ctrlr, "CmdTab");
232 return MODULE_ERR_MALLOC;
234 port->CommandTables[j] = ct_vpage;
235 port->CmdList[j].Flags = 0;
236 port->CmdList[j].PRDTL = 0;
237 port->CmdList[j].PRDBC = 0;
238 AHCI_int_SetAddr(Ctrlr, &port->CmdList[j].CTBA, MM_GetPhysAddr(ct_vpage));
242 LOG("Port #%i: CLB=%p/%P, FB=%p/%P", i,
243 Ctrlr->Ports[i].CmdList, cl_paddr,
244 Ctrlr->Ports[i].RcvdFIS, fis_paddr);
247 // 6. Clear PxSERR with 1 to each implimented bit
248 // > Clear PxIS then IS.IPS before setting PxIE/GHC.IE
249 // 7. Set PxIE with desired interrupts and set GHC.IE
250 for( int i = 0; i < Ctrlr->PortCount; i ++ )
252 tAHCI_Port *port = &Ctrlr->Ports[i];
254 port->MMIO->PxSERR = 0x3FF783;
255 port->MMIO->PxIS = -1;
256 port->MMIO->PxIE = AHCI_PxIS_CPDS|AHCI_PxIS_DSS|AHCI_PxIS_PSS|AHCI_PxIS_DHRS;
258 Ctrlr->MMIO->IS = -1;
259 Ctrlr->MMIO->GHC |= AHCI_GHC_IE;
261 // Start command engine on all implimented ports
262 for( int i = 0; i < Ctrlr->PortCount; i ++ )
264 tAHCI_Port *port = &Ctrlr->Ports[i];
265 port->MMIO->PxCMD |= AHCI_PxCMD_ST|AHCI_PxCMD_FRE;
268 // Detect present ports using:
269 // > PxTFD.STS.BSY = 0, PxTFD.STS.DRQ = 0, and PxSSTS.DET = 3
270 for( int i = 0; i < Ctrlr->PortCount; i ++ )
272 if( Ctrlr->Ports[i].MMIO->PxTFD & (AHCI_PxTFD_STS_BSY|AHCI_PxTFD_STS_DRQ) )
274 if( (Ctrlr->Ports[i].MMIO->PxSSTS & AHCI_PxSSTS_DET) != 3 << AHCI_PxSSTS_DET_ofs )
277 LOG("Port #%i: Connection detected", i);
278 Ctrlr->Ports[i].bPresent = 1;
279 AHCI_QueryDevice(Ctrlr, i);
284 static inline void _flipChars(char *buffer, size_t pairs)
286 for( int i = 0; i < pairs; i ++ )
288 char tmp = buffer[i*2];
289 buffer[i*2] = buffer[i*2+1];
294 void AHCI_QueryDevice(tAHCI_Ctrlr *Ctrlr, int PortNum)
296 tAHCI_Port *const Port = &Ctrlr->Ports[PortNum];
299 AHCI_SendLBA28Cmd(Port, false, 0, 0, 0, ATA_CMD_IDENTIFY_DEVICE, sizeof(data), &data);
300 AHCI_WaitForInterrupt(Port, 1000);
301 // TODO: Check status from command
303 // TODO: on error, mark device as bad and return
305 _flipChars(data.SerialNum, 20/2);
306 _flipChars(data.FirmwareVer, 8/2);
307 _flipChars(data.ModelNumber, 40/2);
308 LOG("data.SerialNum = '%.20s'", data.SerialNum);
309 LOG("data.FirmwareVer = '%.8s'", data.FirmwareVer);
310 LOG("data.ModelNumber = '%.40s'", data.ModelNumber);
313 if( data.Sectors48 != 0 ) {
315 LOG("Size[48] = 0x%X", (Uint64)data.Sectors48);
316 sector_count = data.Sectors48;
320 LOG("Size[28] = 0x%x", data.Sectors28);
321 sector_count = data.Sectors28;
325 char lvmname[4+1+20+1];
326 strcpy(lvmname, "AHCI:");
327 memcpy(lvmname+5, data.SerialNum, 20);
328 for(int i = 20+5; i-- && lvmname[i] == ' '; )
332 Port->LVMHandle = LVM_AddVolume(&gAHCI_VolumeType, lvmname, Port, 512, sector_count);
335 void AHCI_IRQHandler(int UNUSED(IRQ), void *Data)
337 tAHCI_Ctrlr *Ctrlr = Data;
338 tAHCI_Port *port = &Ctrlr->Ports[0];
340 Uint32 IS = Ctrlr->MMIO->IS;
341 Uint32 PI = Ctrlr->MMIO->PI;
342 LOG("Ctrlr->MMIO->IS = %x", IS);
344 for( int i = 0; i < 32; i ++ )
346 if( !(PI & (1 << i)) )
350 AHCI_int_IRQHandlerPort(port);
355 Ctrlr->MMIO->IS = IS;
358 void AHCI_int_IRQHandlerPort(tAHCI_Port *Port)
360 Uint32 PxIS = Port->MMIO->PxIS;
361 LOG("port->MMIO->PxIS = %x", PxIS);
362 Port->LastIS |= PxIS;
363 if( PxIS & AHCI_PxIS_CPDS ) {
364 // Port change detected
367 if( PxIS & AHCI_PxIS_DHRS ) {
368 LOG("Port->RcvdFIS->RFIS = {");
369 LOG(".Status = 0x%02x", Port->RcvdFIS->RFIS.Status);
370 LOG(".Error = 0x%02x", Port->RcvdFIS->RFIS.Error);
374 // Get bitfield of completed commands (Issued but no activity)
375 Uint32 done_commands = Port->IssuedCommands ^ Port->MMIO->PxSACT;
376 LOG("done_commands = %x", done_commands);
377 for( int i = 0; i < 32; i ++ )
379 if( !(done_commands & (1 << i)) )
381 ASSERT( Port->IssuedCommands & (1 << i) );
382 // If complete, post a SHORTWAIT
383 if( Port->CommandThreads[i] ) {
384 LOG("%i - Poking thread %p", i, Port->CommandThreads[i]);
385 Threads_PostEvent(Port->CommandThreads[i], THREAD_EVENT_SHORTWAIT);
386 Port->CommandThreads[i] = NULL;
388 Port->IssuedCommands &= ~(1 << i);
390 Port->MMIO->PxIS = PxIS;
394 int AHCI_ReadSectors(void *Ptr, Uint64 Address, size_t Count, void *Buffer)
396 tAHCI_Port *Port = Ptr;
398 memset(Buffer, 0xFF, Count*512);
400 ASSERT(Count <= 8096/512);
401 if( (Address+Count) < (1 << 24) )
402 AHCI_SendLBA28Cmd(Port, 0, 0, Count, Address, ATA_CMD_READDMA28, Count*512, Buffer);
404 AHCI_SendLBA48Cmd(Port, 0, 0, Count, Address, ATA_CMD_READDMA48, Count*512, Buffer);
405 if( AHCI_WaitForInterrupt(Port, 1000) )
407 //Debug_HexDump("AHCI_ReadSectors", Buffer, Count*512);
408 // TODO: Check status from command
412 int AHCI_WriteSectors(void *Ptr, Uint64 Address, size_t Count, const void *Buffer)
418 int AHCI_int_GetCommandSlot(tAHCI_Port *Port)
420 Mutex_Acquire(&Port->lCommandSlots);
421 Uint32 PxCI = Port->MMIO->PxCI;
422 Uint32 PxSACT = Port->MMIO->PxSACT;
423 for( int i = 0; i < Port->Ctrlr->NCS; i ++ )
425 if( PxCI & (1 << i) )
427 if( PxSACT & (1 << i) )
429 LOG("Allocated slot %i", i);
430 Port->IssuedCommands |= (1 << i);
435 void AHCI_int_StartCommand(tAHCI_Port *Port, int Slot)
437 Port->MMIO->PxCI = 1 << Slot;
438 Mutex_Release(&Port->lCommandSlots);
440 void AHCI_int_CancelCommand(tAHCI_Port *Port, int Slot)
443 Port->IssuedCommands &= ~(1 << Slot);
444 Mutex_Release(&Port->lCommandSlots);
448 int AHCI_SendLBA48Cmd(tAHCI_Port *Port, int bWrite,
449 Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data)
451 struct sSATA_FIS_H2DRegister regs;
453 regs.Type = SATA_FIS_H2DRegister;
454 regs.Flags = 0x80; // [7]: Update to command register
458 regs.SectorNum = LBA;
459 regs.CylLow = LBA >> 8;
460 regs.CylHigh = LBA >> 16;
461 regs.Dev_Head = 0x40|Dev; // TODO: Need others?
463 regs.SectorNumExp = LBA >> 24;
464 regs.CylLowExp = LBA >> 32;
465 regs.CylHighExp = LBA >> 40;
466 regs.FeaturesExp = 0;
468 regs.SectorCount = Sectors;
469 regs.SectorCountExp = 0;
472 LOG("Sending command %02x with %p+0x%x", Cmd, Data, Size);
473 AHCI_DoFIS(Port, bWrite, sizeof(regs), ®s, 0, NULL, Size, Data);
478 int AHCI_SendLBA28Cmd(tAHCI_Port *Port, int bWrite,
479 Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data)
481 struct sSATA_FIS_H2DRegister regs;
483 ASSERT(LBA < (1 << 24));
485 regs.Type = SATA_FIS_H2DRegister;
486 regs.Flags = 0x80; // [7]: Update to command register
490 regs.SectorNum = LBA;
491 regs.CylLow = LBA >> 8;
492 regs.CylHigh = LBA >> 16;
493 regs.Dev_Head = 0x40|Dev|(LBA >> 24);
495 regs.SectorNumExp = 0;
498 regs.FeaturesExp = 0;
500 regs.SectorCount = Sectors;
501 regs.SectorCountExp = 0;
504 LOG("Sending command %02x with %p+0x%x", Cmd, Data, Size);
505 AHCI_DoFIS(Port, bWrite, sizeof(regs), ®s, 0, NULL, Size, Data);
510 int AHCI_ReadFIS(tAHCI_Port *Port,
511 size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
512 size_t InSize, void *InData)
514 return AHCI_DoFIS(Port, 0, CmdSize, CmdData, PktSize, PktData, InSize, InData);
516 int AHCI_WriteFIS(tAHCI_Port *Port,
517 size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
518 size_t OutSize, const void *OutData)
520 return AHCI_DoFIS(Port, 1, CmdSize, CmdData, PktSize, PktData, OutSize, (void*)OutData);
523 int AHCI_DoFIS(tAHCI_Port *Port, int bWrite,
524 size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
525 size_t OutSize, void *OutData)
527 // 1. Obtain a command slot
528 int slot = AHCI_int_GetCommandSlot(Port);
533 struct sAHCI_CmdTable *cmdt = Port->CommandTables[slot];
537 Log_Error("AHCI", "_DoFIS: Command FIS size %i > 64", CmdSize);
540 memcpy(cmdt->CFIS, CmdData, CmdSize);
542 Log_Error("AHCI", "_DoFIS: ATAPI packet size %i > 64", PktSize);
545 memcpy(cmdt->ACMD, PktData, PktSize);
550 while( ofs < OutSize )
552 tPAddr phys = MM_GetPhysAddr( (char*)OutData + ofs );
553 ASSERT( !(phys & 3) );
554 // TODO: must be 4 byte aligned, and handle 64-bit addressing
555 size_t len = MIN(OutSize - ofs, PAGE_SIZE - (phys % PAGE_SIZE));
556 ASSERT( !(len & 1) );
557 ASSERT( len < 4*1024*1024 );
558 LOG("PRDTL[%i] = %P+%i", prdtl, phys, len);
559 // TODO: count must be even.
560 AHCI_int_SetAddr(Port->Ctrlr, &cmdt->PRDT[prdtl].DBA, phys);
561 cmdt->PRDT[prdtl].DBC = len-1;
565 ASSERT(ofs == OutSize);
566 cmdt->PRDT[prdtl-1].DBC |= (1<<31); // Set IOC
568 // TODO: Port multipliers
569 Port->CmdList[slot].PRDTL = prdtl;
570 Port->CmdList[slot].Flags = (bWrite << 6) | (CmdSize / 4);
573 Port->CommandThreads[slot] = Proc_GetCurThread();
574 Threads_ClearEvent(THREAD_EVENT_SHORTWAIT);
577 AHCI_int_StartCommand(Port, slot);
581 AHCI_int_CancelCommand(Port, slot);
585 int AHCI_WaitForInterrupt(tAHCI_Port *Port, unsigned int Timeout)
587 // Set up a timeout callback
588 Threads_ClearEvent(THREAD_EVENT_TIMER);
589 tTimer *timeout = Time_AllocateTimer(NULL, NULL);
590 Time_ScheduleTimer(timeout, Timeout);
592 // Wait until an interrupt arrives or the timeout fires
593 Uint32 ev = Threads_WaitEvents(THREAD_EVENT_SHORTWAIT|THREAD_EVENT_TIMER);
594 Time_FreeTimer(timeout);
596 if( ev & THREAD_EVENT_TIMER ) {
597 Log_Notice("AHCI", "Timeout of %i ms exceeded", Timeout);