int AHCI_Cleanup(void);
// - Hardware init
int AHCI_InitSys(tAHCI_Ctrlr *Ctrlr);
+void AHCI_QueryDevice(tAHCI_Ctrlr *Ctrlr, int PortNum);
+void AHCI_IRQHandler(int UNUSED(IRQ), void *Data);
+void AHCI_int_IRQHandlerPort(tAHCI_Port *Port);
+// - LVM Interface
+int AHCI_ReadSectors(void *Ptr, Uint64 Address, size_t Count, void *Buffer);
+int AHCI_WriteSectors(void *Ptr, Uint64 Address, size_t Count, const void *Buffer);
+// - Low Level
+ int AHCI_SendLBA28Cmd(tAHCI_Port *Port, int bWrite,
+ Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data
+ );
+ int AHCI_SendLBA48Cmd(tAHCI_Port *Port, int bWrite,
+ Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data
+ );
+ int AHCI_DoFIS(tAHCI_Port *Port, int bWrite,
+ size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
+ size_t OutSize, void *OutData
+ );
+ int AHCI_WaitForInterrupt(tAHCI_Port *Port, unsigned int Timeout);
// === GLOABLS ===
MODULE_DEFINE(0, VERSION, AHCI, AHCI_Install, AHCI_Cleanup, "LVM", NULL);
tAHCI_Ctrlr gaAHCI_Controllers[MAX_CONTROLLERS];
+tLVM_VolType gAHCI_VolumeType = {
+ .Name = "AHCI",
+ .Read = AHCI_ReadSectors,
+ .Write = AHCI_WriteSectors
+ };
// === CODE ====
int AHCI_Install(char **Arguments)
}
ctrlr->IRQ = PCI_GetIRQ(id);
+ IRQ_AddHandler(ctrlr->IRQ, AHCI_IRQHandler, ctrlr);
// Prepare MMIO (two pages according to the spec)
ctrlr->MMIO = (void*)MM_MapHWPages(ctrlr->PMemBase, 2);
return ret;
}
+static inline void AHCI_int_SetAddr(tAHCI_Ctrlr *Ctrlr, volatile Uint32 *Addr, tPAddr PAddr)
+{
+ Addr[0] = PAddr;
+ #if PHYS_BITS > 32
+ if(Ctrlr->Supports64Bit)
+ Addr[1] = PAddr >> 32;
+ else
+ #endif
+ Addr[1] = 0;
+}
+
int AHCI_InitSys(tAHCI_Ctrlr *Ctrlr)
{
// 1. Set GHC.AE
if( !(Ctrlr->MMIO->PI & (1 << i)) )
continue ;
volatile struct s_port *port = &Ctrlr->MMIO->Ports[i];
+ Ctrlr->Ports[idx].Ctrlr = Ctrlr;
Ctrlr->Ports[idx].Idx = i;
Ctrlr->Ports[idx].MMIO = port;
idx ++;
// 5. Allocate PxCLB and PxFB for each port (setting PxCMD.FRE once done)
struct sAHCI_RcvdFIS *fis_vpage = NULL;
struct sAHCI_CmdHdr *cl_vpage = NULL;
+ struct sAHCI_CmdTable *ct_vpage = NULL;
for( int i = 0; i < Ctrlr->PortCount; i ++ )
{
+ tAHCI_Port *port = &Ctrlr->Ports[i];
// CLB First (1 KB alignemnt)
if( ((tVAddr)cl_vpage & 0xFFF) == 0 ) {
cl_vpage = AHCI_AllocPage(Ctrlr, "CLB");
if( !cl_vpage )
return MODULE_ERR_MALLOC;
}
- Ctrlr->Ports[i].CmdList = cl_vpage;
+ port->CmdList = cl_vpage;
cl_vpage += 1024/sizeof(*cl_vpage);
- tPAddr cl_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].CmdList);
- Ctrlr->Ports[i].MMIO->PxCLB = cl_paddr;
- #if PHYS_BITS > 32
- Ctrlr->Ports[i].MMIO->PxCLBU = cl_paddr >> 32;
- #endif
+ tPAddr cl_paddr = MM_GetPhysAddr(port->CmdList);
+ AHCI_int_SetAddr(Ctrlr, &port->MMIO->PxCLB, cl_paddr);
// Received FIS Area
// - If there is space for the FIS in the end of the 1K block, use it
fis_vpage ++;
}
tPAddr fis_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].RcvdFIS);
- Ctrlr->Ports[i].MMIO->PxFB = fis_paddr;
- #if PHYS_BITS > 32
- Ctrlr->Ports[i].MMIO->PxFBU = fis_paddr >> 32;
- #endif
+ AHCI_int_SetAddr(Ctrlr, &port->MMIO->PxFB, fis_paddr);
+
+ // Command tables
+ for( int j = 0; j < Ctrlr->NCS; j ++ )
+ {
+ if( ((tVAddr)ct_vpage & 0xFFF) == 0 ) {
+ ct_vpage = AHCI_AllocPage(Ctrlr, "CmdTab");
+ if( !ct_vpage )
+ return MODULE_ERR_MALLOC;
+ }
+ port->CommandTables[j] = ct_vpage;
+ port->CmdList[j].Flags = 0;
+ port->CmdList[j].PRDTL = 0;
+ port->CmdList[j].PRDBC = 0;
+ AHCI_int_SetAddr(Ctrlr, &port->CmdList[j].CTBA, MM_GetPhysAddr(ct_vpage));
+ ct_vpage ++;
+ }
LOG("Port #%i: CLB=%p/%P, FB=%p/%P", i,
Ctrlr->Ports[i].CmdList, cl_paddr,
Ctrlr->Ports[i].RcvdFIS, fis_paddr);
}
+
// 6. Clear PxSERR with 1 to each implimented bit
// > Clear PxIS then IS.IPS before setting PxIE/GHC.IE
// 7. Set PxIE with desired interrupts and set GHC.IE
+ for( int i = 0; i < Ctrlr->PortCount; i ++ )
+ {
+ tAHCI_Port *port = &Ctrlr->Ports[i];
+
+ port->MMIO->PxSERR = 0x3FF783;
+ port->MMIO->PxIS = -1;
+ port->MMIO->PxIE = AHCI_PxIS_CPDS|AHCI_PxIS_DSS|AHCI_PxIS_PSS|AHCI_PxIS_DHRS;
+ }
+ Ctrlr->MMIO->IS = -1;
+ Ctrlr->MMIO->GHC |= AHCI_GHC_IE;
+
+ // Start command engine on all implimented ports
+ for( int i = 0; i < Ctrlr->PortCount; i ++ )
+ {
+ tAHCI_Port *port = &Ctrlr->Ports[i];
+ port->MMIO->PxCMD |= AHCI_PxCMD_ST|AHCI_PxCMD_FRE;
+ }
// Detect present ports using:
// > PxTFD.STS.BSY = 0, PxTFD.STS.DRQ = 0, and PxSSTS.DET = 3
LOG("Port #%i: Connection detected", i);
Ctrlr->Ports[i].bPresent = 1;
- // TODO: Query volumes connected
+ AHCI_QueryDevice(Ctrlr, i);
+ }
+ return 0;
+}
+
+static inline void _flipChars(char *buffer, size_t pairs)
+{
+ for( int i = 0; i < pairs; i ++ )
+ {
+ char tmp = buffer[i*2];
+ buffer[i*2] = buffer[i*2+1];
+ buffer[i*2+1] = tmp;
+ }
+}
+
+void AHCI_QueryDevice(tAHCI_Ctrlr *Ctrlr, int PortNum)
+{
+ tAHCI_Port *const Port = &Ctrlr->Ports[PortNum];
+ tATA_Identify data;
+
+ AHCI_SendLBA28Cmd(Port, false, 0, 0, 0, ATA_CMD_IDENTIFY_DEVICE, sizeof(data), &data);
+ AHCI_WaitForInterrupt(Port, 1000);
+ // TODO: Check status from command
+
+ // TODO: on error, mark device as bad and return
+
+ _flipChars(data.SerialNum, 20/2);
+ _flipChars(data.FirmwareVer, 8/2);
+ _flipChars(data.ModelNumber, 40/2);
+ LOG("data.SerialNum = '%.20s'", data.SerialNum);
+ LOG("data.FirmwareVer = '%.8s'", data.FirmwareVer);
+ LOG("data.ModelNumber = '%.40s'", data.ModelNumber);
+
+ Uint64 sector_count;
+ if( data.Sectors48 != 0 ) {
+ // Use LBA48 size
+ LOG("Size[48] = 0x%X", (Uint64)data.Sectors48);
+ sector_count = data.Sectors48;
+ }
+ else {
+ // Use LBA28 size
+ LOG("Size[28] = 0x%x", data.Sectors28);
+ sector_count = data.Sectors28;
+ }
+
+ // Create LVM name
+ char lvmname[4+1+20+1];
+ strcpy(lvmname, "AHCI:");
+ memcpy(lvmname+5, data.SerialNum, 20);
+ for(int i = 20+5; i-- && lvmname[i] == ' '; )
+ lvmname[i] = '\0';
+
+ // Register with LVM
+ Port->LVMHandle = LVM_AddVolume(&gAHCI_VolumeType, lvmname, Port, 512, sector_count);
+}
+
+void AHCI_IRQHandler(int UNUSED(IRQ), void *Data)
+{
+ tAHCI_Ctrlr *Ctrlr = Data;
+ tAHCI_Port *port = &Ctrlr->Ports[0];
+
+ Uint32 IS = Ctrlr->MMIO->IS;
+ Uint32 PI = Ctrlr->MMIO->PI;
+ LOG("Ctrlr->MMIO->IS = %x", IS);
+
+ for( int i = 0; i < 32; i ++ )
+ {
+ if( !(PI & (1 << i)) )
+ continue ;
+ if( IS & (1 << i) )
+ {
+ AHCI_int_IRQHandlerPort(port);
+ }
+ port ++;
+ }
+
+ Ctrlr->MMIO->IS = IS;
+}
+
+void AHCI_int_IRQHandlerPort(tAHCI_Port *Port)
+{
+ Uint32 PxIS = Port->MMIO->PxIS;
+ LOG("port->MMIO->PxIS = %x", PxIS);
+ Port->LastIS |= PxIS;
+ if( PxIS & AHCI_PxIS_CPDS ) {
+ // Port change detected
+ }
+
+ if( PxIS & AHCI_PxIS_DHRS ) {
+ LOG("Port->RcvdFIS->RFIS = {");
+ LOG(".Status = 0x%02x", Port->RcvdFIS->RFIS.Status);
+ LOG(".Error = 0x%02x", Port->RcvdFIS->RFIS.Error);
+ LOG("}");
+ }
+
+ // Get bitfield of completed commands (Issued but no activity)
+ Uint32 done_commands = Port->IssuedCommands ^ Port->MMIO->PxSACT;
+ LOG("done_commands = %x", done_commands);
+ for( int i = 0; i < 32; i ++ )
+ {
+ if( !(done_commands & (1 << i)) )
+ continue;
+ ASSERT( Port->IssuedCommands & (1 << i) );
+ // If complete, post a SHORTWAIT
+ if( Port->CommandThreads[i] ) {
+ LOG("%i - Poking thread %p", i, Port->CommandThreads[i]);
+ Threads_PostEvent(Port->CommandThreads[i], THREAD_EVENT_SHORTWAIT);
+ Port->CommandThreads[i] = NULL;
+ }
+ Port->IssuedCommands &= ~(1 << i);
+ }
+ Port->MMIO->PxIS = PxIS;
+}
+
+// - LVM Interface
+int AHCI_ReadSectors(void *Ptr, Uint64 Address, size_t Count, void *Buffer)
+{
+ tAHCI_Port *Port = Ptr;
+
+ memset(Buffer, 0xFF, Count*512);
+
+ ASSERT(Count <= 8096/512);
+ if( (Address+Count) < (1 << 24) )
+ AHCI_SendLBA28Cmd(Port, 0, 0, Count, Address, ATA_CMD_READDMA28, Count*512, Buffer);
+ else
+ AHCI_SendLBA48Cmd(Port, 0, 0, Count, Address, ATA_CMD_READDMA48, Count*512, Buffer);
+ if( AHCI_WaitForInterrupt(Port, 1000) )
+ return 0;
+ //Debug_HexDump("AHCI_ReadSectors", Buffer, Count*512);
+ // TODO: Check status from command
+ return Count;
+}
+
+int AHCI_WriteSectors(void *Ptr, Uint64 Address, size_t Count, const void *Buffer)
+{
+ return 0;
+}
+
+// -- Internals
+int AHCI_int_GetCommandSlot(tAHCI_Port *Port)
+{
+ Mutex_Acquire(&Port->lCommandSlots);
+ Uint32 PxCI = Port->MMIO->PxCI;
+ Uint32 PxSACT = Port->MMIO->PxSACT;
+ for( int i = 0; i < Port->Ctrlr->NCS; i ++ )
+ {
+ if( PxCI & (1 << i) )
+ continue ;
+ if( PxSACT & (1 << i) )
+ continue ;
+ LOG("Allocated slot %i", i);
+ Port->IssuedCommands |= (1 << i);
+ return i;
+ }
+ return -1;
+}
+void AHCI_int_StartCommand(tAHCI_Port *Port, int Slot)
+{
+ Port->MMIO->PxCI = 1 << Slot;
+ Mutex_Release(&Port->lCommandSlots);
+}
+void AHCI_int_CancelCommand(tAHCI_Port *Port, int Slot)
+{
+ // Release command
+ Port->IssuedCommands &= ~(1 << Slot);
+ Mutex_Release(&Port->lCommandSlots);
+}
+
+// -- Low Level
+int AHCI_SendLBA48Cmd(tAHCI_Port *Port, int bWrite,
+ Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data)
+{
+ struct sSATA_FIS_H2DRegister regs;
+
+ regs.Type = SATA_FIS_H2DRegister;
+ regs.Flags = 0x80; // [7]: Update to command register
+ regs.Command = Cmd;
+ regs.Features = 0;
+
+ regs.SectorNum = LBA;
+ regs.CylLow = LBA >> 8;
+ regs.CylHigh = LBA >> 16;
+ regs.Dev_Head = 0x40|Dev; // TODO: Need others?
+
+ regs.SectorNumExp = LBA >> 24;
+ regs.CylLowExp = LBA >> 32;
+ regs.CylHighExp = LBA >> 40;
+ regs.FeaturesExp = 0;
+
+ regs.SectorCount = Sectors;
+ regs.SectorCountExp = 0;
+ regs.Control = 0;
+
+ LOG("Sending command %02x with %p+0x%x", Cmd, Data, Size);
+ AHCI_DoFIS(Port, bWrite, sizeof(regs), ®s, 0, NULL, Size, Data);
+
+ return 0;
+}
+
+int AHCI_SendLBA28Cmd(tAHCI_Port *Port, int bWrite,
+ Uint8 Dev, Uint8 Sectors, Uint64 LBA, Uint8 Cmd, size_t Size, void *Data)
+{
+ struct sSATA_FIS_H2DRegister regs;
+
+ ASSERT(LBA < (1 << 24));
+
+ regs.Type = SATA_FIS_H2DRegister;
+ regs.Flags = 0x80; // [7]: Update to command register
+ regs.Command = Cmd;
+ regs.Features = 0;
+
+ regs.SectorNum = LBA;
+ regs.CylLow = LBA >> 8;
+ regs.CylHigh = LBA >> 16;
+ regs.Dev_Head = 0x40|Dev|(LBA >> 24);
+
+ regs.SectorNumExp = 0;
+ regs.CylLowExp = 0;
+ regs.CylHighExp = 0;
+ regs.FeaturesExp = 0;
+
+ regs.SectorCount = Sectors;
+ regs.SectorCountExp = 0;
+ regs.Control = 0;
+
+ LOG("Sending command %02x with %p+0x%x", Cmd, Data, Size);
+ AHCI_DoFIS(Port, bWrite, sizeof(regs), ®s, 0, NULL, Size, Data);
+
+ return 0;
+}
+
+int AHCI_ReadFIS(tAHCI_Port *Port,
+ size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
+ size_t InSize, void *InData)
+{
+ return AHCI_DoFIS(Port, 0, CmdSize, CmdData, PktSize, PktData, InSize, InData);
+}
+int AHCI_WriteFIS(tAHCI_Port *Port,
+ size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
+ size_t OutSize, const void *OutData)
+{
+ return AHCI_DoFIS(Port, 1, CmdSize, CmdData, PktSize, PktData, OutSize, (void*)OutData);
+}
+
+int AHCI_DoFIS(tAHCI_Port *Port, int bWrite,
+ size_t CmdSize, const void *CmdData, size_t PktSize, const void *PktData,
+ size_t OutSize, void *OutData)
+{
+ // 1. Obtain a command slot
+ int slot = AHCI_int_GetCommandSlot(Port);
+ if( slot < 0 ) {
+ return -1;
+ }
+ ASSERT(slot < 32);
+ struct sAHCI_CmdTable *cmdt = Port->CommandTables[slot];
+
+ // 2. Fill commands
+ if( CmdSize > 64 ) {
+ Log_Error("AHCI", "_DoFIS: Command FIS size %i > 64", CmdSize);
+ goto error;
+ }
+ memcpy(cmdt->CFIS, CmdData, CmdSize);
+ if(PktSize > 16) {
+ Log_Error("AHCI", "_DoFIS: ATAPI packet size %i > 64", PktSize);
+ goto error;
+ }
+ memcpy(cmdt->ACMD, PktData, PktSize);
+
+ // 3. Set pointers
+ size_t ofs = 0;
+ int prdtl = 0;
+ while( ofs < OutSize )
+ {
+ tPAddr phys = MM_GetPhysAddr( (char*)OutData + ofs );
+ ASSERT( !(phys & 3) );
+ // TODO: must be 4 byte aligned, and handle 64-bit addressing
+ size_t len = MIN(OutSize - ofs, PAGE_SIZE - (phys % PAGE_SIZE));
+ ASSERT( !(len & 1) );
+ ASSERT( len < 4*1024*1024 );
+ LOG("PRDTL[%i] = %P+%i", prdtl, phys, len);
+ // TODO: count must be even.
+ AHCI_int_SetAddr(Port->Ctrlr, &cmdt->PRDT[prdtl].DBA, phys);
+ cmdt->PRDT[prdtl].DBC = len-1;
+ prdtl ++;
+ ofs += len;
+ }
+ ASSERT(ofs == OutSize);
+ cmdt->PRDT[prdtl-1].DBC |= (1<<31); // Set IOC
+
+ // TODO: Port multipliers
+ Port->CmdList[slot].PRDTL = prdtl;
+ Port->CmdList[slot].Flags = (bWrite << 6) | (CmdSize / 4);
+
+ // Prepare interrupt
+ Port->CommandThreads[slot] = Proc_GetCurThread();
+ Threads_ClearEvent(THREAD_EVENT_SHORTWAIT);
+
+ // 4. Dispatch
+ AHCI_int_StartCommand(Port, slot);
+
+ return 0;
+error:
+ AHCI_int_CancelCommand(Port, slot);
+ return -1;
+}
+
+int AHCI_WaitForInterrupt(tAHCI_Port *Port, unsigned int Timeout)
+{
+ // Set up a timeout callback
+ Threads_ClearEvent(THREAD_EVENT_TIMER);
+ tTimer *timeout = Time_AllocateTimer(NULL, NULL);
+ Time_ScheduleTimer(timeout, Timeout);
+
+ // Wait until an interrupt arrives or the timeout fires
+ Uint32 ev = Threads_WaitEvents(THREAD_EVENT_SHORTWAIT|THREAD_EVENT_TIMER);
+ Time_FreeTimer(timeout);
+
+ if( ev & THREAD_EVENT_TIMER ) {
+ Log_Notice("AHCI", "Timeout of %i ms exceeded", Timeout);
+ return 1;
}
return 0;
}