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