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