]>
Commit | Line | Data |
---|---|---|
8e60fc8f VS |
1 | /* pxe.c - Driver to provide access to the pxe filesystem */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
4 | * Copyright (C) 2008,2009,2011 Free Software Foundation, Inc. | |
5 | * | |
6 | * GRUB is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * GRUB is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <grub/dl.h> | |
21 | #include <grub/net.h> | |
22 | #include <grub/mm.h> | |
23 | #include <grub/file.h> | |
24 | #include <grub/misc.h> | |
8e60fc8f | 25 | #include <grub/env.h> |
9c4b5c13 | 26 | #include <grub/i18n.h> |
3c491b47 | 27 | #include <grub/loader.h> |
8e60fc8f VS |
28 | |
29 | #include <grub/machine/pxe.h> | |
30 | #include <grub/machine/int.h> | |
31 | #include <grub/machine/memory.h> | |
6708faaf | 32 | #include <grub/machine/kernel.h> |
8e60fc8f VS |
33 | |
34 | GRUB_MOD_LICENSE ("GPLv3+"); | |
35 | ||
36 | #define SEGMENT(x) ((x) >> 4) | |
37 | #define OFFSET(x) ((x) & 0xF) | |
38 | #define SEGOFS(x) ((SEGMENT(x) << 16) + OFFSET(x)) | |
39 | #define LINEAR(x) (void *) ((((x) >> 16) << 4) + ((x) & 0xFFFF)) | |
40 | ||
41 | struct grub_pxe_undi_open | |
42 | { | |
43 | grub_uint16_t status; | |
44 | grub_uint16_t open_flag; | |
45 | grub_uint16_t pkt_filter; | |
46 | grub_uint16_t mcast_count; | |
47 | grub_uint8_t mcast[8][6]; | |
7e47e27b | 48 | } GRUB_PACKED; |
8e60fc8f | 49 | |
6708faaf VS |
50 | struct grub_pxe_undi_info |
51 | { | |
52 | grub_uint16_t status; | |
53 | grub_uint16_t base_io; | |
54 | grub_uint16_t int_number; | |
55 | grub_uint16_t mtu; | |
56 | grub_uint16_t hwtype; | |
57 | grub_uint16_t hwaddrlen; | |
58 | grub_uint8_t current_addr[16]; | |
59 | grub_uint8_t permanent_addr[16]; | |
60 | grub_uint32_t romaddr; | |
61 | grub_uint16_t rxbufct; | |
62 | grub_uint16_t txbufct; | |
7e47e27b | 63 | } GRUB_PACKED; |
6708faaf VS |
64 | |
65 | ||
8e60fc8f VS |
66 | struct grub_pxe_undi_isr |
67 | { | |
68 | grub_uint16_t status; | |
69 | grub_uint16_t func_flag; | |
70 | grub_uint16_t buffer_len; | |
71 | grub_uint16_t frame_len; | |
72 | grub_uint16_t frame_hdr_len; | |
73 | grub_uint32_t buffer; | |
74 | grub_uint8_t prot_type; | |
75 | grub_uint8_t pkt_type; | |
7e47e27b | 76 | } GRUB_PACKED; |
8e60fc8f VS |
77 | |
78 | enum | |
79 | { | |
80 | GRUB_PXE_ISR_IN_START = 1, | |
81 | GRUB_PXE_ISR_IN_PROCESS, | |
82 | GRUB_PXE_ISR_IN_GET_NEXT | |
83 | }; | |
84 | ||
85 | enum | |
86 | { | |
87 | GRUB_PXE_ISR_OUT_OURS = 0, | |
88 | GRUB_PXE_ISR_OUT_NOT_OURS = 1 | |
89 | }; | |
90 | ||
91 | enum | |
92 | { | |
93 | GRUB_PXE_ISR_OUT_DONE = 0, | |
94 | GRUB_PXE_ISR_OUT_TRANSMIT = 2, | |
95 | GRUB_PXE_ISR_OUT_RECEIVE = 3, | |
96 | GRUB_PXE_ISR_OUT_BUSY = 4, | |
97 | }; | |
98 | ||
99 | struct grub_pxe_undi_transmit | |
100 | { | |
101 | grub_uint16_t status; | |
102 | grub_uint8_t protocol; | |
103 | grub_uint8_t xmitflag; | |
104 | grub_uint32_t dest; | |
105 | grub_uint32_t tbd; | |
106 | grub_uint32_t reserved[2]; | |
7e47e27b | 107 | } GRUB_PACKED; |
8e60fc8f VS |
108 | |
109 | struct grub_pxe_undi_tbd | |
110 | { | |
111 | grub_uint16_t len; | |
112 | grub_uint32_t buf; | |
113 | grub_uint16_t blk_count; | |
114 | struct | |
115 | { | |
116 | grub_uint8_t ptr_type; | |
117 | grub_uint8_t reserved; | |
118 | grub_uint16_t len; | |
119 | grub_uint32_t ptr; | |
120 | } blocks[8]; | |
7e47e27b | 121 | } GRUB_PACKED; |
8e60fc8f VS |
122 | |
123 | struct grub_pxe_bangpxe *grub_pxe_pxenv; | |
124 | static grub_uint32_t pxe_rm_entry = 0; | |
125 | ||
126 | static struct grub_pxe_bangpxe * | |
127 | grub_pxe_scan (void) | |
128 | { | |
129 | struct grub_bios_int_registers regs; | |
130 | struct grub_pxenv *pxenv; | |
131 | struct grub_pxe_bangpxe *bangpxe; | |
132 | ||
133 | regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; | |
134 | ||
135 | regs.ebx = 0; | |
136 | regs.ecx = 0; | |
137 | regs.eax = 0x5650; | |
138 | regs.es = 0; | |
139 | ||
140 | grub_bios_interrupt (0x1a, ®s); | |
141 | ||
142 | if ((regs.eax & 0xffff) != 0x564e) | |
143 | return NULL; | |
144 | ||
145 | pxenv = (struct grub_pxenv *) ((regs.es << 4) + (regs.ebx & 0xffff)); | |
146 | if (grub_memcmp (pxenv->signature, GRUB_PXE_SIGNATURE, | |
147 | sizeof (pxenv->signature)) | |
148 | != 0) | |
149 | return NULL; | |
150 | ||
151 | if (pxenv->version < 0x201) | |
152 | return NULL; | |
153 | ||
154 | bangpxe = (void *) ((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4) | |
155 | + (pxenv->pxe_ptr & 0xffff)); | |
156 | ||
157 | if (!bangpxe) | |
158 | return NULL; | |
159 | ||
160 | if (grub_memcmp (bangpxe->signature, GRUB_PXE_BANGPXE_SIGNATURE, | |
161 | sizeof (bangpxe->signature)) != 0) | |
162 | return NULL; | |
163 | ||
164 | pxe_rm_entry = bangpxe->rm_entry; | |
165 | ||
166 | return bangpxe; | |
167 | } | |
168 | ||
e2955971 | 169 | static struct grub_net_buff * |
3e747239 | 170 | grub_pxe_recv (struct grub_net_card *dev __attribute__ ((unused))) |
8e60fc8f VS |
171 | { |
172 | struct grub_pxe_undi_isr *isr; | |
173 | static int in_progress = 0; | |
5c62099a | 174 | grub_uint8_t *ptr, *end; |
e2955971 | 175 | struct grub_net_buff *buf; |
8e60fc8f VS |
176 | |
177 | isr = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; | |
178 | ||
179 | if (!in_progress) | |
180 | { | |
181 | grub_memset (isr, 0, sizeof (*isr)); | |
182 | isr->func_flag = GRUB_PXE_ISR_IN_START; | |
183 | grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry); | |
e555f379 SG |
184 | /* Normally according to the specification we should also check |
185 | that isr->func_flag != GRUB_PXE_ISR_OUT_OURS but unfortunately it | |
186 | breaks on intel cards. | |
187 | */ | |
188 | if (isr->status) | |
59b455fc VS |
189 | { |
190 | in_progress = 0; | |
e2955971 | 191 | return NULL; |
59b455fc | 192 | } |
8e60fc8f VS |
193 | grub_memset (isr, 0, sizeof (*isr)); |
194 | isr->func_flag = GRUB_PXE_ISR_IN_PROCESS; | |
195 | grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry); | |
196 | } | |
197 | else | |
198 | { | |
199 | grub_memset (isr, 0, sizeof (*isr)); | |
200 | isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT; | |
201 | grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry); | |
202 | } | |
203 | ||
204 | while (isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE) | |
205 | { | |
206 | if (isr->status || isr->func_flag == GRUB_PXE_ISR_OUT_DONE) | |
59b455fc VS |
207 | { |
208 | in_progress = 0; | |
e2955971 | 209 | return NULL; |
59b455fc | 210 | } |
8e60fc8f VS |
211 | grub_memset (isr, 0, sizeof (*isr)); |
212 | isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT; | |
213 | grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry); | |
214 | } | |
215 | ||
531e2241 | 216 | buf = grub_netbuff_alloc (isr->frame_len + 2); |
e2955971 VS |
217 | if (!buf) |
218 | return NULL; | |
219 | /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible | |
220 | by 4. So that IP header is aligned on 4 bytes. */ | |
221 | grub_netbuff_reserve (buf, 2); | |
222 | if (!buf) | |
223 | { | |
224 | grub_netbuff_free (buf); | |
225 | return NULL; | |
226 | } | |
8e60fc8f VS |
227 | ptr = buf->data; |
228 | end = ptr + isr->frame_len; | |
e2955971 | 229 | grub_netbuff_put (buf, isr->frame_len); |
8e60fc8f VS |
230 | grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len); |
231 | ptr += isr->buffer_len; | |
232 | while (ptr < end) | |
233 | { | |
234 | grub_memset (isr, 0, sizeof (*isr)); | |
235 | isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT; | |
236 | grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry); | |
237 | if (isr->status || isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE) | |
59b455fc VS |
238 | { |
239 | in_progress = 1; | |
e2955971 VS |
240 | grub_netbuff_free (buf); |
241 | return NULL; | |
59b455fc | 242 | } |
8e60fc8f VS |
243 | |
244 | grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len); | |
245 | ptr += isr->buffer_len; | |
246 | } | |
59b455fc | 247 | in_progress = 1; |
8e60fc8f | 248 | |
e2955971 | 249 | return buf; |
8e60fc8f VS |
250 | } |
251 | ||
252 | static grub_err_t | |
3e747239 | 253 | grub_pxe_send (struct grub_net_card *dev __attribute__ ((unused)), |
8e60fc8f VS |
254 | struct grub_net_buff *pack) |
255 | { | |
256 | struct grub_pxe_undi_transmit *trans; | |
257 | struct grub_pxe_undi_tbd *tbd; | |
258 | char *buf; | |
259 | ||
260 | trans = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; | |
261 | grub_memset (trans, 0, sizeof (*trans)); | |
262 | tbd = (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 128); | |
263 | grub_memset (tbd, 0, sizeof (*tbd)); | |
264 | buf = (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 256); | |
265 | grub_memcpy (buf, pack->data, pack->tail - pack->data); | |
266 | ||
267 | trans->tbd = SEGOFS ((grub_addr_t) tbd); | |
268 | trans->protocol = 0; | |
269 | tbd->len = pack->tail - pack->data; | |
270 | tbd->buf = SEGOFS ((grub_addr_t) buf); | |
271 | ||
272 | grub_pxe_call (GRUB_PXENV_UNDI_TRANSMIT, trans, pxe_rm_entry); | |
273 | if (trans->status) | |
9c4b5c13 | 274 | return grub_error (GRUB_ERR_IO, N_("couldn't send network packet")); |
8e60fc8f VS |
275 | return 0; |
276 | } | |
277 | ||
72b9ad93 | 278 | static void |
3e747239 | 279 | grub_pxe_close (struct grub_net_card *dev __attribute__ ((unused))) |
8e60fc8f | 280 | { |
6708faaf | 281 | if (pxe_rm_entry) |
671a78ac VS |
282 | grub_pxe_call (GRUB_PXENV_UNDI_CLOSE, |
283 | (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, | |
284 | pxe_rm_entry); | |
671a78ac VS |
285 | } |
286 | ||
287 | static grub_err_t | |
3e747239 | 288 | grub_pxe_open (struct grub_net_card *dev __attribute__ ((unused))) |
671a78ac VS |
289 | { |
290 | struct grub_pxe_undi_open *ou; | |
291 | ou = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; | |
292 | grub_memset (ou, 0, sizeof (*ou)); | |
293 | ou->pkt_filter = 4; | |
294 | grub_pxe_call (GRUB_PXENV_UNDI_OPEN, ou, pxe_rm_entry); | |
295 | ||
296 | if (ou->status) | |
0bc2cd0f | 297 | return grub_error (GRUB_ERR_IO, "can't open UNDI"); |
671a78ac VS |
298 | return GRUB_ERR_NONE; |
299 | } | |
300 | ||
0bc2cd0f VS |
301 | struct grub_net_card_driver grub_pxe_card_driver = |
302 | { | |
303 | .open = grub_pxe_open, | |
304 | .close = grub_pxe_close, | |
305 | .send = grub_pxe_send, | |
306 | .recv = grub_pxe_recv | |
307 | }; | |
308 | ||
309 | struct grub_net_card grub_pxe_card = | |
310 | { | |
311 | .driver = &grub_pxe_card_driver, | |
312 | .name = "pxe" | |
313 | }; | |
8e60fc8f | 314 | |
3c491b47 VS |
315 | static grub_err_t |
316 | grub_pxe_shutdown (int flags) | |
317 | { | |
318 | if (flags & GRUB_LOADER_FLAG_PXE_NOT_UNLOAD) | |
319 | return GRUB_ERR_NONE; | |
320 | if (!pxe_rm_entry) | |
321 | return GRUB_ERR_NONE; | |
322 | ||
323 | grub_pxe_call (GRUB_PXENV_UNDI_CLOSE, | |
324 | (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, | |
325 | pxe_rm_entry); | |
326 | grub_pxe_call (GRUB_PXENV_UNDI_SHUTDOWN, | |
327 | (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, | |
328 | pxe_rm_entry); | |
329 | grub_pxe_call (GRUB_PXENV_UNLOAD_STACK, | |
330 | (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, | |
331 | pxe_rm_entry); | |
332 | pxe_rm_entry = 0; | |
333 | grub_net_card_unregister (&grub_pxe_card); | |
334 | ||
335 | return GRUB_ERR_NONE; | |
336 | } | |
337 | ||
338 | /* Nothing we can do. */ | |
339 | static grub_err_t | |
340 | grub_pxe_restore (void) | |
341 | { | |
342 | return GRUB_ERR_NONE; | |
343 | } | |
344 | ||
0a96117d VS |
345 | void * |
346 | grub_pxe_get_cached (grub_uint16_t type) | |
8e60fc8f | 347 | { |
6708faaf | 348 | struct grub_pxenv_get_cached_info ci; |
0a96117d | 349 | ci.packet_type = type; |
8e60fc8f VS |
350 | ci.buffer = 0; |
351 | ci.buffer_size = 0; | |
352 | grub_pxe_call (GRUB_PXENV_GET_CACHED_INFO, &ci, pxe_rm_entry); | |
353 | if (ci.status) | |
0a96117d VS |
354 | return 0; |
355 | ||
356 | return LINEAR (ci.buffer); | |
357 | } | |
8e60fc8f | 358 | |
0a96117d VS |
359 | static void |
360 | grub_pc_net_config_real (char **device, char **path) | |
361 | { | |
362 | struct grub_net_bootp_packet *bp; | |
363 | ||
364 | bp = grub_pxe_get_cached (GRUB_PXENV_PACKET_TYPE_DHCP_ACK); | |
8e60fc8f | 365 | |
0a96117d VS |
366 | if (!bp) |
367 | return; | |
6708faaf VS |
368 | grub_net_configure_by_dhcp_ack ("pxe", &grub_pxe_card, 0, |
369 | bp, GRUB_PXE_BOOTP_SIZE, | |
370 | 1, device, path); | |
671a78ac | 371 | |
6708faaf VS |
372 | } |
373 | ||
3c491b47 VS |
374 | static struct grub_preboot *fini_hnd; |
375 | ||
6708faaf VS |
376 | GRUB_MOD_INIT(pxe) |
377 | { | |
378 | struct grub_pxe_bangpxe *pxenv; | |
6708faaf VS |
379 | struct grub_pxe_undi_info *ui; |
380 | unsigned i; | |
381 | ||
382 | pxenv = grub_pxe_scan (); | |
383 | if (! pxenv) | |
384 | return; | |
385 | ||
386 | ui = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; | |
387 | grub_memset (ui, 0, sizeof (*ui)); | |
388 | grub_pxe_call (GRUB_PXENV_UNDI_GET_INFORMATION, ui, pxe_rm_entry); | |
389 | ||
390 | grub_memcpy (grub_pxe_card.default_address.mac, ui->current_addr, | |
391 | sizeof (grub_pxe_card.default_address.mac)); | |
392 | for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++) | |
393 | if (grub_pxe_card.default_address.mac[i] != 0) | |
394 | break; | |
395 | if (i != sizeof (grub_pxe_card.default_address.mac)) | |
396 | { | |
397 | for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++) | |
398 | if (grub_pxe_card.default_address.mac[i] != 0xff) | |
399 | break; | |
400 | } | |
401 | if (i == sizeof (grub_pxe_card.default_address.mac)) | |
402 | grub_memcpy (grub_pxe_card.default_address.mac, ui->permanent_addr, | |
403 | sizeof (grub_pxe_card.default_address.mac)); | |
6a1af81a | 404 | grub_pxe_card.mtu = ui->mtu; |
6708faaf | 405 | |
8e60fc8f VS |
406 | grub_pxe_card.default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; |
407 | ||
8e60fc8f | 408 | grub_net_card_register (&grub_pxe_card); |
6708faaf | 409 | grub_pc_net_config = grub_pc_net_config_real; |
3c491b47 VS |
410 | fini_hnd = grub_loader_register_preboot_hook (grub_pxe_shutdown, |
411 | grub_pxe_restore, | |
412 | GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK); | |
8e60fc8f VS |
413 | } |
414 | ||
415 | GRUB_MOD_FINI(pxe) | |
416 | { | |
6708faaf | 417 | grub_pc_net_config = 0; |
3c491b47 VS |
418 | grub_pxe_shutdown (0); |
419 | grub_loader_unregister_preboot_hook (fini_hnd); | |
8e60fc8f | 420 | } |