Create the AHCI branch for Aman's work
[reactos.git] / boot / freeldr / freeldr / lib / fs / pxe.c
1 /*
2 * FreeLoader
3 * Copyright (C) 2011 Hervé Poussineau
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <freeldr.h>
21
22 #define NDEBUG
23 #include <debug.h>
24
25 #define TAG_PXE_FILE 'FexP'
26 #define NO_FILE ((ULONG)-1)
27
28 DBG_DEFAULT_CHANNEL(FILESYSTEM);
29
30 static IP4 _ServerIP = { 0, };
31 static ULONG _OpenFile = NO_FILE;
32 static CHAR _OpenFileName[128];
33 static ULONG _FileSize = 0;
34 static ULONG _FilePosition = 0;
35 static ULONG _PacketPosition = 0;
36 static UCHAR _Packet[4096];
37 static UCHAR* _CachedFile = NULL;
38 static ULONG _CachedLength = 0;
39
40 static PPXE
41 FindPxeStructure(VOID)
42 {
43 PPXE Ptr;
44 UCHAR Checksum;
45 UCHAR i;
46
47 /* Find the '!PXE' structure */
48 Ptr = (PPXE)0xA0000;
49 while ((ULONG)Ptr > 0x10000)
50 {
51 Ptr = (PPXE)((ULONG)Ptr - 0x10);
52
53 /* Look for signature */
54 if (memcmp(Ptr, "!PXE", 4) != 0)
55 continue;
56
57 /* Check size */
58 if (Ptr->StructLength != sizeof(PXE))
59 continue;
60
61 /* Check checksum */
62 Checksum = 0;
63 for (i = 0; i < Ptr->StructLength; i++)
64 Checksum += *((PUCHAR)Ptr + i);
65 if (Checksum != 0)
66 continue;
67
68 TRACE("!PXE structure found at %p\n", Ptr);
69 return Ptr;
70 }
71
72 return NULL;
73 }
74
75 static PPXE GetPxeStructure(VOID)
76 {
77 static PPXE pPxe = NULL;
78 static BOOLEAN bPxeSearched = FALSE;
79 if (!bPxeSearched)
80 {
81 pPxe = FindPxeStructure();
82 bPxeSearched = TRUE;
83 }
84 return pPxe;
85 }
86
87 extern PXENV_EXIT __cdecl PxeCallApi(UINT16 Segment, UINT16 Offset, UINT16 Service, VOID *Parameter);
88 BOOLEAN CallPxe(UINT16 Service, PVOID Parameter)
89 {
90 PPXE pxe;
91 PXENV_EXIT exit;
92
93 pxe = GetPxeStructure();
94 if (!pxe)
95 return FALSE;
96
97 if (Service != PXENV_TFTP_READ)
98 {
99 // HACK: this delay shouldn't be necessary
100 KeStallExecutionProcessor(100 * 1000); // 100 ms
101 TRACE("PxeCall(0x%x, %p)\n", Service, Parameter);
102 }
103
104 exit = PxeCallApi(pxe->EntryPointSP.segment, pxe->EntryPointSP.offset, Service, Parameter);
105 if (exit != PXENV_EXIT_SUCCESS)
106 {
107 ERR("PxeCall(0x%x, %p) failed with exit=%d status=0x%x\n",
108 Service, Parameter, exit, *(PXENV_STATUS*)Parameter);
109 return FALSE;
110 }
111 if (*(PXENV_STATUS*)Parameter != PXENV_STATUS_SUCCESS)
112 {
113 ERR("PxeCall(0x%x, %p) succeeded, but returned error status 0x%x\n",
114 Service, Parameter, *(PXENV_STATUS*)Parameter);
115 return FALSE;
116 }
117 return TRUE;
118 }
119
120 static ARC_STATUS PxeClose(ULONG FileId)
121 {
122 t_PXENV_TFTP_CLOSE closeData;
123
124 if (_OpenFile == NO_FILE || FileId != _OpenFile)
125 return EBADF;
126
127 RtlZeroMemory(&closeData, sizeof(closeData));
128 if (!CallPxe(PXENV_TFTP_CLOSE, &closeData))
129 return EIO;
130
131 _OpenFile = NO_FILE;
132 if (_CachedFile)
133 {
134 FrLdrTempFree(_CachedFile, TAG_PXE_FILE);
135 _CachedFile = NULL;
136 }
137 return ESUCCESS;
138 }
139
140 static ARC_STATUS PxeGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
141 {
142 if (_OpenFile == NO_FILE || FileId != _OpenFile)
143 return EBADF;
144
145 RtlZeroMemory(Information, sizeof(*Information));
146 Information->EndingAddress.LowPart = _FileSize;
147 Information->CurrentAddress.LowPart = _FilePosition;
148
149 return ESUCCESS;
150 }
151
152 static ARC_STATUS PxeOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
153 {
154 t_PXENV_TFTP_GET_FSIZE sizeData;
155 t_PXENV_TFTP_OPEN openData;
156 SIZE_T PathLen, i;
157
158 if (_OpenFile != NO_FILE)
159 return EIO;
160 if (OpenMode != OpenReadOnly)
161 return EACCES;
162
163 /* Retrieve the path length without NULL terminator */
164 PathLen = (Path ? min(strlen(Path), sizeof(_OpenFileName) - 1) : 0);
165
166 /* Lowercase the path and always use slashes as separators */
167 for (i = 0; i < PathLen; i++)
168 {
169 if (Path[i] == '\\')
170 _OpenFileName[i] = '/';
171 else
172 _OpenFileName[i] = tolower(Path[i]);
173 }
174
175 /* Zero out rest of the file name */
176 RtlZeroMemory(_OpenFileName + PathLen, sizeof(_OpenFileName) - PathLen);
177
178 RtlZeroMemory(&sizeData, sizeof(sizeData));
179 sizeData.ServerIPAddress = _ServerIP;
180 RtlCopyMemory(sizeData.FileName, _OpenFileName, sizeof(_OpenFileName));
181 if (!CallPxe(PXENV_TFTP_GET_FSIZE, &sizeData))
182 {
183 ERR("Failed to get '%s' size\n", Path);
184 return EIO;
185 }
186
187 _FileSize = sizeData.FileSize;
188 if (_FileSize < 1024 * 1024)
189 {
190 _CachedFile = FrLdrTempAlloc(_FileSize, TAG_PXE_FILE);
191 // Don't check for allocation failure, we support _CachedFile == NULL
192 }
193 _CachedLength = 0;
194
195 RtlZeroMemory(&openData, sizeof(openData));
196 openData.ServerIPAddress = _ServerIP;
197 RtlCopyMemory(openData.FileName, _OpenFileName, sizeof(_OpenFileName));
198 openData.PacketSize = sizeof(_Packet);
199
200 if (!CallPxe(PXENV_TFTP_OPEN, &openData))
201 {
202 if (_CachedFile)
203 {
204 FrLdrTempFree(_CachedFile, TAG_PXE_FILE);
205 _CachedFile = NULL;
206 }
207 return ENOENT;
208 }
209
210 _FilePosition = 0;
211 _PacketPosition = 0;
212
213 _OpenFile = *FileId;
214 return ESUCCESS;
215 }
216
217 static ARC_STATUS PxeRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
218 {
219 t_PXENV_TFTP_READ readData;
220 ULONG i;
221
222 *Count = 0;
223
224 if (_OpenFile == NO_FILE || FileId != _OpenFile)
225 return EBADF;
226
227 RtlZeroMemory(&readData, sizeof(readData));
228 readData.Buffer.segment = ((UINT32)_Packet & 0xf0000) / 16;
229 readData.Buffer.offset = (UINT32)_Packet & 0xffff;
230
231 // Get new packets as required
232 while (N > 0)
233 {
234 if (N < _CachedLength - _FilePosition)
235 i = N;
236 else
237 i = _CachedLength - _FilePosition;
238 if (_CachedFile)
239 RtlCopyMemory(Buffer, _CachedFile + _FilePosition, i);
240 else
241 RtlCopyMemory(Buffer, _Packet + _FilePosition - _PacketPosition, i);
242 _FilePosition += i;
243 Buffer = (UCHAR*)Buffer + i;
244 *Count += i;
245 N -= i;
246 if (N == 0)
247 break;
248
249 if (!CallPxe(PXENV_TFTP_READ, &readData))
250 return EIO;
251 if (_CachedFile)
252 RtlCopyMemory(_CachedFile + _CachedLength, _Packet, readData.BufferSize);
253 _PacketPosition = _CachedLength;
254 _CachedLength += readData.BufferSize;
255 }
256
257 return ESUCCESS;
258 }
259
260 static ARC_STATUS PxeSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
261 {
262 t_PXENV_TFTP_READ readData;
263
264 if (_OpenFile == NO_FILE || FileId != _OpenFile)
265 return EBADF;
266
267 if (Position->HighPart != 0 || SeekMode != SeekAbsolute)
268 return EINVAL;
269
270 if (!_CachedFile && Position->LowPart < _FilePosition)
271 {
272 // Close and reopen the file to go to position 0
273 if (PxeClose(FileId) != ESUCCESS)
274 return EIO;
275 if (PxeOpen(_OpenFileName, OpenReadOnly, &FileId) != ESUCCESS)
276 return EIO;
277 }
278
279 RtlZeroMemory(&readData, sizeof(readData));
280 readData.Buffer.segment = ((UINT32)_Packet & 0xf0000) / 16;
281 readData.Buffer.offset = (UINT32)_Packet & 0xffff;
282
283 // Get new packets as required
284 while (Position->LowPart > _CachedLength)
285 {
286 if (!CallPxe(PXENV_TFTP_READ, &readData))
287 return EIO;
288 if (_CachedFile)
289 {
290 RtlCopyMemory(_CachedFile + _CachedLength, _Packet, readData.BufferSize);
291 }
292 _PacketPosition = _CachedLength;
293 _CachedLength += readData.BufferSize;
294 }
295
296 _FilePosition = Position->LowPart;
297 return ESUCCESS;
298 }
299
300 static const DEVVTBL PxeVtbl = {
301 PxeClose,
302 PxeGetFileInformation,
303 PxeOpen,
304 PxeRead,
305 PxeSeek,
306 };
307
308 const DEVVTBL* PxeMount(ULONG DeviceId)
309 {
310 if (GetPxeStructure() == NULL)
311 return NULL;
312 return &PxeVtbl;
313 }
314
315 static ARC_STATUS PxeDiskClose(ULONG FileId)
316 {
317 // Nothing to do
318 return ESUCCESS;
319 }
320
321 static ARC_STATUS PxeDiskGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
322 {
323 // No disk access in PXE mode
324 return EINVAL;
325 }
326
327 static ARC_STATUS PxeDiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
328 {
329 // Nothing to do
330 return ESUCCESS;
331 }
332
333 static ARC_STATUS PxeDiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
334 {
335 // No disk access in PXE mode
336 return EINVAL;
337 }
338
339 static ARC_STATUS PxeDiskSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
340 {
341 // No disk access in PXE mode
342 return EINVAL;
343 }
344
345 static const DEVVTBL PxeDiskVtbl = {
346 PxeDiskClose,
347 PxeDiskGetFileInformation,
348 PxeDiskOpen,
349 PxeDiskRead,
350 PxeDiskSeek,
351 };
352
353 static BOOLEAN GetCachedInfo(VOID)
354 {
355 t_PXENV_GET_CACHED_INFO Data;
356 BOOLEAN res;
357 UCHAR* Packet;
358
359 RtlZeroMemory(&Data, sizeof(Data));
360 Data.PacketType = PXENV_PACKET_TYPE_CACHED_REPLY;
361
362 res = CallPxe(PXENV_GET_CACHED_INFO, &Data);
363 if (!res)
364 return FALSE;
365 if (Data.BufferSize < 36)
366 return FALSE;
367 Packet = (UCHAR*)((UINT32)(Data.Buffer.segment << 4) + Data.Buffer.offset);
368 RtlCopyMemory(&_ServerIP, Packet + 20, sizeof(IP4));
369 return TRUE;
370 }
371
372 BOOLEAN PxeInit(VOID)
373 {
374 static BOOLEAN Initialized = FALSE;
375 static BOOLEAN Success = FALSE;
376
377 // Do initialization only once
378 if (Initialized)
379 return Success;
380 Initialized = TRUE;
381
382 // Check if PXE is available
383 if (GetPxeStructure() && GetCachedInfo())
384 {
385 FsRegisterDevice("net(0)", &PxeDiskVtbl);
386 Success = TRUE;
387 }
388
389 return Success;
390 }
391