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