[HAL][FREELDR] Fix system timer oscillator frequency on a Xbox (#2245)
[reactos.git] / boot / freeldr / freeldr / arch / i386 / hardware.c
1 /*
2 * FreeLoader
3 *
4 * Copyright (C) 2003, 2004 Eric Kohl
5 * Copyright (C) 2009 Hervé Poussineau
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include <freeldr.h>
23
24 #include <debug.h>
25 DBG_DEFAULT_CHANNEL(HWDETECT);
26
27 #define MILLISEC (10)
28 #define PRECISION (8)
29
30 #if defined(SARCH_XBOX)
31 #define CLOCK_TICK_RATE 1125000
32 #else
33 #define CLOCK_TICK_RATE 1193182
34 #endif
35
36 #define HZ (100)
37 #define LATCH (CLOCK_TICK_RATE / HZ)
38
39 static unsigned int delay_count = 1;
40
41 /* Used for BIOS disks pre-enumeration performed when detecting the boot devices in InitializeBootDevices() */
42 extern UCHAR PcBiosDiskCount;
43
44 /* This function is slightly different in its PC and XBOX versions */
45 GET_HARDDISK_CONFIG_DATA GetHarddiskConfigurationData = NULL;
46
47 PCHAR
48 GetHarddiskIdentifier(UCHAR DriveNumber);
49
50 /* FUNCTIONS *****************************************************************/
51
52 static
53 VOID
54 __StallExecutionProcessor(ULONG Loops)
55 {
56 register volatile unsigned int i;
57 for (i = 0; i < Loops; i++);
58 }
59
60 VOID StallExecutionProcessor(ULONG Microseconds)
61 {
62 ULONGLONG LoopCount = ((ULONGLONG)delay_count * (ULONGLONG)Microseconds) / 1000ULL;
63 __StallExecutionProcessor((ULONG)LoopCount);
64 }
65
66 static
67 ULONG
68 Read8254Timer(VOID)
69 {
70 ULONG Count;
71
72 WRITE_PORT_UCHAR((PUCHAR)0x43, 0x00);
73 Count = READ_PORT_UCHAR((PUCHAR)0x40);
74 Count |= READ_PORT_UCHAR((PUCHAR)0x40) << 8;
75
76 return Count;
77 }
78
79 static
80 VOID
81 WaitFor8254Wraparound(VOID)
82 {
83 ULONG CurCount;
84 ULONG PrevCount = ~0;
85 LONG Delta;
86
87 CurCount = Read8254Timer();
88
89 do
90 {
91 PrevCount = CurCount;
92 CurCount = Read8254Timer();
93 Delta = CurCount - PrevCount;
94
95 /*
96 * This limit for delta seems arbitrary, but it isn't, it's
97 * slightly above the level of error a buggy Mercury/Neptune
98 * chipset timer can cause.
99 */
100 }
101 while (Delta < 300);
102 }
103
104 VOID
105 HalpCalibrateStallExecution(VOID)
106 {
107 ULONG i;
108 ULONG calib_bit;
109 ULONG CurCount;
110
111 /* Initialise timer interrupt with MILLISECOND ms interval */
112 WRITE_PORT_UCHAR((PUCHAR)0x43, 0x34); /* binary, mode 2, LSB/MSB, ch 0 */
113 WRITE_PORT_UCHAR((PUCHAR)0x40, LATCH & 0xff); /* LSB */
114 WRITE_PORT_UCHAR((PUCHAR)0x40, LATCH >> 8); /* MSB */
115
116 /* Stage 1: Coarse calibration */
117
118 delay_count = 1;
119
120 do
121 {
122 /* Next delay count to try */
123 delay_count <<= 1;
124
125 WaitFor8254Wraparound();
126
127 /* Do the delay */
128 __StallExecutionProcessor(delay_count);
129
130 CurCount = Read8254Timer();
131 }
132 while (CurCount > LATCH / 2);
133
134 /* Get bottom value for delay */
135 delay_count >>= 1;
136
137 /* Stage 2: Fine calibration */
138
139 /* Which bit are we going to test */
140 calib_bit = delay_count;
141
142 for (i = 0; i < PRECISION; i++)
143 {
144 /* Next bit to calibrate */
145 calib_bit >>= 1;
146
147 /* If we have done all bits, stop */
148 if (!calib_bit) break;
149
150 /* Set the bit in delay_count */
151 delay_count |= calib_bit;
152
153 WaitFor8254Wraparound();
154
155 /* Do the delay */
156 __StallExecutionProcessor(delay_count);
157
158 CurCount = Read8254Timer();
159 /* If a tick has passed, turn the calibrated bit back off */
160 if (CurCount <= LATCH / 2)
161 delay_count &= ~calib_bit;
162 }
163
164 /* We're finished: Do the finishing touches */
165
166 /* Calculate delay_count for 1ms */
167 delay_count /= (MILLISEC / 2);
168 }
169
170
171 static
172 UCHAR
173 GetFloppyType(UCHAR DriveNumber)
174 {
175 UCHAR Data;
176
177 WRITE_PORT_UCHAR((PUCHAR)0x70, 0x10);
178 Data = READ_PORT_UCHAR((PUCHAR)0x71);
179
180 if (DriveNumber == 0)
181 return Data >> 4;
182 else if (DriveNumber == 1)
183 return Data & 0x0F;
184
185 return 0;
186 }
187
188 static
189 PVOID
190 GetInt1eTable(VOID)
191 {
192 PUSHORT SegPtr = (PUSHORT)0x7A;
193 PUSHORT OfsPtr = (PUSHORT)0x78;
194
195 return (PVOID)((ULONG_PTR)(((ULONG)(*SegPtr)) << 4) + (ULONG)(*OfsPtr));
196 }
197
198 static
199 VOID
200 DetectBiosFloppyPeripheral(PCONFIGURATION_COMPONENT_DATA ControllerKey)
201 {
202 PCM_PARTIAL_RESOURCE_LIST PartialResourceList;
203 PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptor;
204 PCM_FLOPPY_DEVICE_DATA FloppyData;
205 CHAR Identifier[20];
206 PCONFIGURATION_COMPONENT_DATA PeripheralKey;
207 ULONG Size;
208 UCHAR FloppyNumber;
209 UCHAR FloppyType;
210 ULONG MaxDensity[6] = {0, 360, 1200, 720, 1440, 2880};
211 PUCHAR Ptr;
212
213 for (FloppyNumber = 0; FloppyNumber < 2; FloppyNumber++)
214 {
215 FloppyType = GetFloppyType(FloppyNumber);
216
217 if ((FloppyType > 5) || (FloppyType == 0))
218 continue;
219
220 if (!DiskResetController(FloppyNumber))
221 continue;
222
223 Ptr = GetInt1eTable();
224
225 /* Set 'Identifier' value */
226 sprintf(Identifier, "FLOPPY%d", FloppyNumber + 1);
227
228 Size = sizeof(CM_PARTIAL_RESOURCE_LIST) +
229 sizeof(CM_FLOPPY_DEVICE_DATA);
230 PartialResourceList = FrLdrHeapAlloc(Size, TAG_HW_RESOURCE_LIST);
231 if (PartialResourceList == NULL)
232 {
233 ERR("Failed to allocate resource descriptor\n");
234 return;
235 }
236
237 memset(PartialResourceList, 0, Size);
238 PartialResourceList->Version = 1;
239 PartialResourceList->Revision = 1;
240 PartialResourceList->Count = 1;
241
242 PartialDescriptor = &PartialResourceList->PartialDescriptors[0];
243 PartialDescriptor->Type = CmResourceTypeDeviceSpecific;
244 PartialDescriptor->ShareDisposition = CmResourceShareUndetermined;
245 PartialDescriptor->u.DeviceSpecificData.DataSize = sizeof(CM_FLOPPY_DEVICE_DATA);
246
247 FloppyData = (PVOID)(((ULONG_PTR)PartialResourceList) + sizeof(CM_PARTIAL_RESOURCE_LIST));
248 FloppyData->Version = 2;
249 FloppyData->Revision = 0;
250 FloppyData->MaxDensity = MaxDensity[FloppyType];
251 FloppyData->MountDensity = 0;
252 RtlCopyMemory(&FloppyData->StepRateHeadUnloadTime, Ptr, 11);
253 FloppyData->MaximumTrackValue = (FloppyType == 1) ? 39 : 79;
254 FloppyData->DataTransferRate = 0;
255
256 FldrCreateComponentKey(ControllerKey,
257 PeripheralClass,
258 FloppyDiskPeripheral,
259 Input | Output,
260 FloppyNumber,
261 0xFFFFFFFF,
262 Identifier,
263 PartialResourceList,
264 Size,
265 &PeripheralKey);
266 }
267 }
268
269 static
270 PCONFIGURATION_COMPONENT_DATA
271 DetectBiosFloppyController(PCONFIGURATION_COMPONENT_DATA BusKey)
272 {
273 PCONFIGURATION_COMPONENT_DATA ControllerKey;
274 PCM_PARTIAL_RESOURCE_LIST PartialResourceList;
275 PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptor;
276 ULONG Size;
277 ULONG FloppyCount;
278
279 FloppyCount = MachGetFloppyCount();
280 TRACE("Floppy count: %u\n", FloppyCount);
281
282 /* Always create a BIOS disk controller, no matter if we have floppy drives or not */
283 Size = sizeof(CM_PARTIAL_RESOURCE_LIST) +
284 2 * sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR);
285 PartialResourceList = FrLdrHeapAlloc(Size, TAG_HW_RESOURCE_LIST);
286 if (PartialResourceList == NULL)
287 {
288 ERR("Failed to allocate resource descriptor\n");
289 return NULL;
290 }
291 memset(PartialResourceList, 0, Size);
292
293 /* Initialize resource descriptor */
294 PartialResourceList->Version = 1;
295 PartialResourceList->Revision = 1;
296 PartialResourceList->Count = 3;
297
298 /* Set IO Port */
299 PartialDescriptor = &PartialResourceList->PartialDescriptors[0];
300 PartialDescriptor->Type = CmResourceTypePort;
301 PartialDescriptor->ShareDisposition = CmResourceShareDeviceExclusive;
302 PartialDescriptor->Flags = CM_RESOURCE_PORT_IO;
303 PartialDescriptor->u.Port.Start.LowPart = 0x03F0;
304 PartialDescriptor->u.Port.Start.HighPart = 0x0;
305 PartialDescriptor->u.Port.Length = 8;
306
307 /* Set Interrupt */
308 PartialDescriptor = &PartialResourceList->PartialDescriptors[1];
309 PartialDescriptor->Type = CmResourceTypeInterrupt;
310 PartialDescriptor->ShareDisposition = CmResourceShareUndetermined;
311 PartialDescriptor->Flags = CM_RESOURCE_INTERRUPT_LATCHED;
312 PartialDescriptor->u.Interrupt.Level = 6;
313 PartialDescriptor->u.Interrupt.Vector = 6;
314 PartialDescriptor->u.Interrupt.Affinity = 0xFFFFFFFF;
315
316 /* Set DMA channel */
317 PartialDescriptor = &PartialResourceList->PartialDescriptors[2];
318 PartialDescriptor->Type = CmResourceTypeDma;
319 PartialDescriptor->ShareDisposition = CmResourceShareUndetermined;
320 PartialDescriptor->Flags = 0;
321 PartialDescriptor->u.Dma.Channel = 2;
322 PartialDescriptor->u.Dma.Port = 0;
323
324 /* Create floppy disk controller */
325 FldrCreateComponentKey(BusKey,
326 ControllerClass,
327 DiskController,
328 Output | Input,
329 0x0,
330 0xFFFFFFFF,
331 NULL,
332 PartialResourceList,
333 Size,
334 &ControllerKey);
335 TRACE("Created key: DiskController\\0\n");
336
337 if (FloppyCount)
338 DetectBiosFloppyPeripheral(ControllerKey);
339
340 return ControllerKey;
341 }
342
343 VOID
344 DetectBiosDisks(PCONFIGURATION_COMPONENT_DATA SystemKey,
345 PCONFIGURATION_COMPONENT_DATA BusKey)
346 {
347 PCONFIGURATION_COMPONENT_DATA ControllerKey, DiskKey;
348 PCM_PARTIAL_RESOURCE_LIST PartialResourceList;
349 PCM_INT13_DRIVE_PARAMETER Int13Drives;
350 GEOMETRY Geometry;
351 UCHAR DiskCount, DriveNumber;
352 USHORT i;
353 ULONG Size;
354
355 /* The pre-enumeration of the BIOS disks was already done in InitializeBootDevices() */
356 DiskCount = PcBiosDiskCount;
357
358 /* Use the floppy disk controller as our controller */
359 ControllerKey = DetectBiosFloppyController(BusKey);
360 if (!ControllerKey)
361 {
362 ERR("Failed to detect BIOS disk controller\n");
363 return;
364 }
365
366 /* Allocate resource descriptor */
367 Size = sizeof(CM_PARTIAL_RESOURCE_LIST) +
368 sizeof(CM_INT13_DRIVE_PARAMETER) * DiskCount;
369 PartialResourceList = FrLdrHeapAlloc(Size, TAG_HW_RESOURCE_LIST);
370 if (PartialResourceList == NULL)
371 {
372 ERR("Failed to allocate resource descriptor\n");
373 return;
374 }
375
376 /* Initialize resource descriptor */
377 memset(PartialResourceList, 0, Size);
378 PartialResourceList->Version = 1;
379 PartialResourceList->Revision = 1;
380 PartialResourceList->Count = 1;
381 PartialResourceList->PartialDescriptors[0].Type = CmResourceTypeDeviceSpecific;
382 PartialResourceList->PartialDescriptors[0].ShareDisposition = 0;
383 PartialResourceList->PartialDescriptors[0].Flags = 0;
384 PartialResourceList->PartialDescriptors[0].u.DeviceSpecificData.DataSize =
385 sizeof(CM_INT13_DRIVE_PARAMETER) * DiskCount;
386
387 /* Get harddisk Int13 geometry data */
388 Int13Drives = (PVOID)(((ULONG_PTR)PartialResourceList) + sizeof(CM_PARTIAL_RESOURCE_LIST));
389 for (i = 0; i < DiskCount; i++)
390 {
391 DriveNumber = 0x80 + i;
392
393 if (MachDiskGetDriveGeometry(DriveNumber, &Geometry))
394 {
395 Int13Drives[i].DriveSelect = DriveNumber;
396 Int13Drives[i].MaxCylinders = Geometry.Cylinders - 1;
397 Int13Drives[i].SectorsPerTrack = (USHORT)Geometry.Sectors;
398 Int13Drives[i].MaxHeads = (USHORT)Geometry.Heads - 1;
399 Int13Drives[i].NumberDrives = DiskCount;
400
401 TRACE("Disk %x: %u Cylinders %u Heads %u Sectors %u Bytes\n",
402 DriveNumber,
403 Geometry.Cylinders - 1,
404 Geometry.Heads - 1,
405 Geometry.Sectors,
406 Geometry.BytesPerSector);
407 }
408 }
409
410 /* Update the 'System' key's configuration data with BIOS INT13h information */
411 FldrSetConfigurationData(SystemKey, PartialResourceList, Size);
412
413 /* Create and fill subkey for each harddisk */
414 for (i = 0; i < DiskCount; i++)
415 {
416 PCHAR Identifier;
417
418 DriveNumber = 0x80 + i;
419
420 /* Get disk values */
421 PartialResourceList = GetHarddiskConfigurationData(DriveNumber, &Size);
422 Identifier = GetHarddiskIdentifier(DriveNumber);
423
424 /* Create disk key */
425 FldrCreateComponentKey(ControllerKey,
426 PeripheralClass,
427 DiskPeripheral,
428 Output | Input,
429 0x0,
430 0xFFFFFFFF,
431 Identifier,
432 PartialResourceList,
433 Size,
434 &DiskKey);
435 }
436 }
437
438 VOID
439 FrLdrCheckCpuCompatibility(VOID)
440 {
441 INT CpuInformation[4] = {-1};
442 ULONG NumberOfIds;
443
444 /* Check if the processor first supports ID 1 */
445 __cpuid(CpuInformation, 0);
446
447 NumberOfIds = CpuInformation[0];
448
449 if (NumberOfIds == 0)
450 {
451 FrLdrBugCheckWithMessage(MISSING_HARDWARE_REQUIREMENTS,
452 __FILE__,
453 __LINE__,
454 "ReactOS requires the CPUID instruction to return "
455 "more than one supported ID.\n\n");
456 }
457
458 /* NumberOfIds will be greater than 1 if the processor is new enough */
459 if (NumberOfIds == 1)
460 {
461 INT ProcessorFamily;
462
463 /* Get information */
464 __cpuid(CpuInformation, 1);
465
466 ProcessorFamily = (CpuInformation[0] >> 8) & 0xF;
467
468 /* If it's Family 4 or lower, bugcheck */
469 if (ProcessorFamily < 5)
470 {
471 FrLdrBugCheckWithMessage(MISSING_HARDWARE_REQUIREMENTS,
472 __FILE__,
473 __LINE__,
474 "Processor is too old (family %u < 5)\n"
475 "ReactOS requires a Pentium-level processor or newer.",
476 ProcessorFamily);
477 }
478 }
479 }
480
481 /* EOF */