Modules/AHCI - Added H2D register structure (used for commands)
[tpg/acess2.git] / KernelLand / Modules / Storage / AHCI / ahci.c
1 /*
2  * Acess2 Kernel - AHCI Driver
3  * - By John Hodge (thePowersGang)
4  *
5  * ahci.c
6  * - Driver core
7  */
8 #define DEBUG   1
9 #define VERSION 0x0001
10 #include <acess.h>
11 #include <timers.h>
12 #include <modules.h>
13 #include <drv_pci.h>
14 #include "ahci.h"
15
16 // === CONSTANTS ===
17 #define MAX_CONTROLLERS 4
18
19 // === PROTOTYPES ===
20  int    AHCI_Install(char **Arguments);
21  int    AHCI_Cleanup(void);
22 // - Hardware init
23  int    AHCI_InitSys(tAHCI_Ctrlr *Ctrlr);
24
25 // === GLOABLS ===
26 MODULE_DEFINE(0, VERSION, AHCI, AHCI_Install, AHCI_Cleanup, "LVM", NULL);
27 tAHCI_Ctrlr     gaAHCI_Controllers[MAX_CONTROLLERS];
28
29 // === CODE ====
30 int AHCI_Install(char **Arguments)
31 {
32          int    rv;
33         LOG("offsetof(struct sAHCI_MemSpace, Ports) = %x", offsetof(struct sAHCI_MemSpace, Ports));
34         ASSERT( offsetof(struct sAHCI_MemSpace, Ports) == 0x100 );
35
36         // 0106XX = Standard, 010400 = RAID
37          int    i = 0;
38          int    id = -1;
39         while( (id = PCI_GetDeviceByClass(0x010601, 0xFFFFFF, id)) >= 0 && i < MAX_CONTROLLERS )
40         {
41                 tAHCI_Ctrlr *ctrlr = &gaAHCI_Controllers[i];
42                 ctrlr->PMemBase = PCI_GetBAR(id, 5);
43                 // 
44                 if( !ctrlr->PMemBase )
45                 {
46                         // ctrlr->PMemBase = PCI_AllocateBAR(id, 5);
47                         // TODO: Allocate space
48                         continue ;
49                 }
50                 // - IO Address?
51                 if( (ctrlr->PMemBase & 0x1FFF) ) {
52                         Log_Warning("AHCI", "Controller %i [PCI %i] is invalid (BAR5=%P)",
53                                 i, id, ctrlr->PMemBase);
54                         continue;
55                 }
56                 
57                 ctrlr->IRQ = PCI_GetIRQ(id);
58
59                 // Prepare MMIO (two pages according to the spec)
60                 ctrlr->MMIO = (void*)MM_MapHWPages(ctrlr->PMemBase, 2);
61
62                 LOG("%i [%i]: %P/IRQ%i mapped to %p",
63                         i, id, ctrlr->PMemBase, ctrlr->IRQ, ctrlr->MMIO);
64
65                 LOG(" CAP = %x, PI = %x, VS = %x",
66                         ctrlr->MMIO->CAP, ctrlr->MMIO->PI, ctrlr->MMIO->VS);
67
68                 if( (rv = AHCI_InitSys(ctrlr)) ) {
69                         // Clean up controller's allocations
70                         // TODO: Should an init error cause module unload?
71                         return rv;
72                 }       
73
74                 i ++;
75         }
76         if( id >= 0 ) {
77                 Log_Notice("AHCI", "Only up to %i controllers are supported", MAX_CONTROLLERS);
78         }
79         
80         return 0;
81 }
82
83 int AHCI_Cleanup(void)
84 {
85         return 0;
86 }
87
88 static inline void *AHCI_AllocPage(tAHCI_Ctrlr *Ctrlr, const char *AllocName)
89 {
90         void    *ret;
91         #if PHYS_BITS > 32
92         if( Ctrlr->Supports64Bit )
93                 ret = (void*)MM_AllocDMA(1, 64, NULL);
94         else
95         #endif
96                 ret = (void*)MM_AllocDMA(1, 32, NULL);
97         if( !ret ) {
98                 Log_Error("AHCI", "Unable to allocate page for '%s'", AllocName);
99                 return NULL;
100         }
101         return ret;
102 }
103
104 int AHCI_InitSys(tAHCI_Ctrlr *Ctrlr)
105 {
106         // 1. Set GHC.AE
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 ++ )
111         {
112                 if( !(Ctrlr->MMIO->PI & (1 << i)) )
113                         continue ;
114                 
115                 Ctrlr->PortCount ++;
116                 
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 )
121                         continue ;
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)
124                 port->PxCMD = 0;
125                 basetime = now();
126                 //  > On timeout, port/HBA reset
127         }
128         Ctrlr->Ports = malloc( Ctrlr->PortCount * sizeof(*Ctrlr->Ports) );
129         if( !Ctrlr->Ports ) {
130                 return MODULE_ERR_MALLOC;
131         }
132         // - Process timeouts after all ports have been poked, saves time
133         for( int i = 0, idx = 0; i < 32; i ++ )
134         {
135                 if( !(Ctrlr->MMIO->PI & (1 << i)) )
136                         continue ;
137                 volatile struct s_port  *port = &Ctrlr->MMIO->Ports[i];
138                 Ctrlr->Ports[idx].Idx = i;
139                 Ctrlr->Ports[idx].MMIO = port;
140                 idx ++;
141         
142                 while( (port->PxCMD & (AHCI_PxCMD_CR|AHCI_PxCMD_FR)) && now()-basetime < 500 )
143                         Time_Delay(10);
144                 
145                 if( (port->PxCMD & (AHCI_PxCMD_CR|AHCI_PxCMD_FR)) )
146                 {
147                         Log_Error("AHCI", "Port %i did not return to idle", i);
148                 }
149         }
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 ++ )
156         {
157                 // CLB First (1 KB alignemnt)
158                 if( ((tVAddr)cl_vpage & 0xFFF) == 0 ) {
159                         cl_vpage = AHCI_AllocPage(Ctrlr, "CLB");
160                         if( !cl_vpage )
161                                 return MODULE_ERR_MALLOC;
162                 }
163                 Ctrlr->Ports[i].CmdList = cl_vpage;
164                 cl_vpage += 1024/sizeof(*cl_vpage);
165
166                 tPAddr  cl_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].CmdList);
167                 Ctrlr->Ports[i].MMIO->PxCLB  = cl_paddr;
168                 #if PHYS_BITS > 32
169                 Ctrlr->Ports[i].MMIO->PxCLBU = cl_paddr >> 32;
170                 #endif
171
172                 // Received FIS Area
173                 // - If there is space for the FIS in the end of the 1K block, use it
174                 if( Ctrlr->NCS <= (1024-256)/32 )
175                 {
176                         Ctrlr->Ports[i].RcvdFIS = (void*)(cl_vpage - 256/32);
177                 }
178                 else
179                 {
180                         if( ((tVAddr)fis_vpage & 0xFFF) == 0 ) {
181                                 fis_vpage = AHCI_AllocPage(Ctrlr, "FIS");
182                                 if( !fis_vpage )
183                                         return MODULE_ERR_MALLOC;
184                         }
185                         Ctrlr->Ports[i].RcvdFIS = fis_vpage;
186                         fis_vpage ++;
187                 }
188                 tPAddr  fis_paddr = MM_GetPhysAddr(Ctrlr->Ports[i].RcvdFIS);
189                 Ctrlr->Ports[i].MMIO->PxFB  = fis_paddr;
190                 #if PHYS_BITS > 32
191                 Ctrlr->Ports[i].MMIO->PxFBU = fis_paddr >> 32;
192                 #endif
193                 
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);
197         }
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
201         
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 ++ )
205         {
206                 if( Ctrlr->Ports[i].MMIO->PxTFD & (AHCI_PxTFD_STS_BSY|AHCI_PxTFD_STS_DRQ) )
207                         continue ;
208                 if( (Ctrlr->Ports[i].MMIO->PxSSTS & AHCI_PxSSTS_DET) != 3 << AHCI_PxSSTS_DET_ofs )
209                         continue ;
210                 
211                 LOG("Port #%i: Connection detected", i);
212                 Ctrlr->Ports[i].bPresent = 1;
213                 // TODO: Query volumes connected
214         }
215         return 0;
216 }
217

UCC git Repository :: git.ucc.asn.au