[FREELDR] PXE: Fix network packet size
[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 return ESUCCESS;
149 }
150
151 static ARC_STATUS PxeOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
152 {
153 t_PXENV_TFTP_GET_FSIZE sizeData;
154 t_PXENV_TFTP_OPEN openData;
155 SIZE_T PathLen, i;
156
157 if (_OpenFile != NO_FILE)
158 return EIO;
159 if (OpenMode != OpenReadOnly)
160 return EACCES;
161
162 /* Retrieve the path length without NULL terminator */
163 PathLen = (Path ? min(strlen(Path), sizeof(_OpenFileName) - 1) : 0);
164
165 /* Lowercase the path and always use slashes as separators */
166 for (i = 0; i < PathLen; i++)
167 {
168 if (Path[i] == '\\')
169 _OpenFileName[i] = '/';
170 else
171 _OpenFileName[i] = tolower(Path[i]);
172 }
173
174 /* Zero out rest of the file name */
175 RtlZeroMemory(_OpenFileName + PathLen, sizeof(_OpenFileName) - PathLen);
176
177 RtlZeroMemory(&sizeData, sizeof(sizeData));
178 sizeData.ServerIPAddress = _ServerIP;
179 RtlCopyMemory(sizeData.FileName, _OpenFileName, sizeof(_OpenFileName));
180 if (!CallPxe(PXENV_TFTP_GET_FSIZE, &sizeData))
181 {
182 ERR("Failed to get '%s' size\n", Path);
183 return EIO;
184 }
185
186 _FileSize = sizeData.FileSize;
187 if (_FileSize < 1024 * 1024)
188 {
189 _CachedFile = FrLdrTempAlloc(_FileSize, TAG_PXE_FILE);
190 // Don't check for allocation failure, we support _CachedFile == NULL
191 }
192 _CachedLength = 0;
193
194 RtlZeroMemory(&openData, sizeof(openData));
195 openData.ServerIPAddress = _ServerIP;
196 RtlCopyMemory(openData.FileName, _OpenFileName, sizeof(_OpenFileName));
197 openData.PacketSize = sizeof(_Packet);
198
199 if (!CallPxe(PXENV_TFTP_OPEN, &openData))
200 {
201 if (_CachedFile)
202 {
203 FrLdrTempFree(_CachedFile, TAG_PXE_FILE);
204 _CachedFile = NULL;
205 }
206 return ENOENT;
207 }
208
209 _FilePosition = 0;
210 _PacketPosition = 0;
211
212 _OpenFile = *FileId;
213 return ESUCCESS;
214 }
215
216 static ARC_STATUS PxeRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
217 {
218 t_PXENV_TFTP_READ readData;
219 ULONG i;
220
221 *Count = 0;
222
223 if (_OpenFile == NO_FILE || FileId != _OpenFile)
224 return EBADF;
225
226 RtlZeroMemory(&readData, sizeof(readData));
227 readData.Buffer.segment = ((ULONG_PTR)_Packet & 0xf0000) / 16;
228 readData.Buffer.offset = (ULONG_PTR)_Packet & 0xffff;
229
230 // Get new packets as required
231 while (N > 0)
232 {
233 if (N < _CachedLength - _FilePosition)
234 i = N;
235 else
236 i = _CachedLength - _FilePosition;
237 if (_CachedFile)
238 RtlCopyMemory(Buffer, _CachedFile + _FilePosition, i);
239 else
240 RtlCopyMemory(Buffer, _Packet + _FilePosition - _PacketPosition, i);
241 _FilePosition += i;
242 Buffer = (UCHAR*)Buffer + i;
243 *Count += i;
244 N -= i;
245 if (N == 0)
246 break;
247
248 if (!CallPxe(PXENV_TFTP_READ, &readData))
249 return EIO;
250 if (_CachedFile)
251 RtlCopyMemory(_CachedFile + _CachedLength, _Packet, readData.BufferSize);
252 _PacketPosition = _CachedLength;
253 _CachedLength += readData.BufferSize;
254 }
255
256 return ESUCCESS;
257 }
258
259 static ARC_STATUS PxeSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
260 {
261 t_PXENV_TFTP_READ readData;
262
263 if (_OpenFile == NO_FILE || FileId != _OpenFile)
264 return EBADF;
265
266 if (Position->HighPart != 0 || SeekMode != SeekAbsolute)
267 return EINVAL;
268
269 if (!_CachedFile && Position->LowPart < _FilePosition)
270 {
271 // Close and reopen the file to go to position 0
272 if (PxeClose(FileId) != ESUCCESS)
273 return EIO;
274 if (PxeOpen(_OpenFileName, OpenReadOnly, &FileId) != ESUCCESS)
275 return EIO;
276 }
277
278 RtlZeroMemory(&readData, sizeof(readData));
279 readData.Buffer.segment = ((ULONG_PTR)_Packet & 0xf0000) / 16;
280 readData.Buffer.offset = (ULONG_PTR)_Packet & 0xffff;
281
282 // Get new packets as required
283 while (Position->LowPart > _CachedLength)
284 {
285 if (!CallPxe(PXENV_TFTP_READ, &readData))
286 return EIO;
287 if (_CachedFile)
288 {
289 RtlCopyMemory(_CachedFile + _CachedLength, _Packet, readData.BufferSize);
290 }
291 _PacketPosition = _CachedLength;
292 _CachedLength += readData.BufferSize;
293 }
294
295 _FilePosition = Position->LowPart;
296 return ESUCCESS;
297 }
298
299 static const DEVVTBL PxeVtbl = {
300 PxeClose,
301 PxeGetFileInformation,
302 PxeOpen,
303 PxeRead,
304 PxeSeek,
305 };
306
307 const DEVVTBL* PxeMount(ULONG DeviceId)
308 {
309 if (GetPxeStructure() == NULL)
310 return NULL;
311 return &PxeVtbl;
312 }
313
314 static ARC_STATUS PxeDiskClose(ULONG FileId)
315 {
316 // Nothing to do
317 return ESUCCESS;
318 }
319
320 static ARC_STATUS PxeDiskGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
321 {
322 // No disk access in PXE mode
323 return EINVAL;
324 }
325
326 static ARC_STATUS PxeDiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
327 {
328 // Nothing to do
329 return ESUCCESS;
330 }
331
332 static ARC_STATUS PxeDiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
333 {
334 // No disk access in PXE mode
335 return EINVAL;
336 }
337
338 static ARC_STATUS PxeDiskSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
339 {
340 // No disk access in PXE mode
341 return EINVAL;
342 }
343
344 static const DEVVTBL PxeDiskVtbl = {
345 PxeDiskClose,
346 PxeDiskGetFileInformation,
347 PxeDiskOpen,
348 PxeDiskRead,
349 PxeDiskSeek,
350 };
351
352 static BOOLEAN GetCachedInfo(VOID)
353 {
354 t_PXENV_GET_CACHED_INFO Data;
355 BOOLEAN res;
356 UCHAR* Packet;
357
358 RtlZeroMemory(&Data, sizeof(Data));
359 Data.PacketType = PXENV_PACKET_TYPE_CACHED_REPLY;
360
361 res = CallPxe(PXENV_GET_CACHED_INFO, &Data);
362 if (!res)
363 return FALSE;
364 if (Data.BufferSize < 36)
365 return FALSE;
366 Packet = (UCHAR*)((ULONG_PTR)(Data.Buffer.segment << 4) + Data.Buffer.offset);
367 RtlCopyMemory(&_ServerIP, Packet + 20, sizeof(IP4));
368 return TRUE;
369 }
370
371 BOOLEAN PxeInit(VOID)
372 {
373 static BOOLEAN Initialized = FALSE;
374 static BOOLEAN Success = FALSE;
375
376 // Do initialization only once
377 if (Initialized)
378 return Success;
379 Initialized = TRUE;
380
381 // Check if PXE is available
382 if (GetPxeStructure() && GetCachedInfo())
383 {
384 FsRegisterDevice("net(0)", &PxeDiskVtbl);
385 Success = TRUE;
386 }
387
388 return Success;
389 }
390