summaryrefslogtreecommitdiffstats
path: root/grub-core/net/drivers/i386/pc/pxe.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/net/drivers/i386/pc/pxe.c')
-rw-r--r--grub-core/net/drivers/i386/pc/pxe.c419
1 files changed, 419 insertions, 0 deletions
diff --git a/grub-core/net/drivers/i386/pc/pxe.c b/grub-core/net/drivers/i386/pc/pxe.c
new file mode 100644
index 0000000..3f4152d
--- /dev/null
+++ b/grub-core/net/drivers/i386/pc/pxe.c
@@ -0,0 +1,419 @@
+/* pxe.c - Driver to provide access to the pxe filesystem */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008,2009,2011 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/net.h>
+#include <grub/mm.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/env.h>
+#include <grub/i18n.h>
+#include <grub/loader.h>
+
+#include <grub/machine/pxe.h>
+#include <grub/machine/int.h>
+#include <grub/machine/memory.h>
+#include <grub/machine/kernel.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define SEGMENT(x) ((x) >> 4)
+#define OFFSET(x) ((x) & 0xF)
+#define SEGOFS(x) ((SEGMENT(x) << 16) + OFFSET(x))
+#define LINEAR(x) (void *) ((((x) >> 16) << 4) + ((x) & 0xFFFF))
+
+struct grub_pxe_undi_open
+{
+ grub_uint16_t status;
+ grub_uint16_t open_flag;
+ grub_uint16_t pkt_filter;
+ grub_uint16_t mcast_count;
+ grub_uint8_t mcast[8][6];
+} GRUB_PACKED;
+
+struct grub_pxe_undi_info
+{
+ grub_uint16_t status;
+ grub_uint16_t base_io;
+ grub_uint16_t int_number;
+ grub_uint16_t mtu;
+ grub_uint16_t hwtype;
+ grub_uint16_t hwaddrlen;
+ grub_uint8_t current_addr[16];
+ grub_uint8_t permanent_addr[16];
+ grub_uint32_t romaddr;
+ grub_uint16_t rxbufct;
+ grub_uint16_t txbufct;
+} GRUB_PACKED;
+
+
+struct grub_pxe_undi_isr
+{
+ grub_uint16_t status;
+ grub_uint16_t func_flag;
+ grub_uint16_t buffer_len;
+ grub_uint16_t frame_len;
+ grub_uint16_t frame_hdr_len;
+ grub_uint32_t buffer;
+ grub_uint8_t prot_type;
+ grub_uint8_t pkt_type;
+} GRUB_PACKED;
+
+enum
+ {
+ GRUB_PXE_ISR_IN_START = 1,
+ GRUB_PXE_ISR_IN_PROCESS,
+ GRUB_PXE_ISR_IN_GET_NEXT
+ };
+
+enum
+ {
+ GRUB_PXE_ISR_OUT_OURS = 0,
+ GRUB_PXE_ISR_OUT_NOT_OURS = 1
+ };
+
+enum
+ {
+ GRUB_PXE_ISR_OUT_DONE = 0,
+ GRUB_PXE_ISR_OUT_TRANSMIT = 2,
+ GRUB_PXE_ISR_OUT_RECEIVE = 3,
+ GRUB_PXE_ISR_OUT_BUSY = 4,
+ };
+
+struct grub_pxe_undi_transmit
+{
+ grub_uint16_t status;
+ grub_uint8_t protocol;
+ grub_uint8_t xmitflag;
+ grub_uint32_t dest;
+ grub_uint32_t tbd;
+ grub_uint32_t reserved[2];
+} GRUB_PACKED;
+
+struct grub_pxe_undi_tbd
+{
+ grub_uint16_t len;
+ grub_uint32_t buf;
+ grub_uint16_t blk_count;
+ struct
+ {
+ grub_uint8_t ptr_type;
+ grub_uint8_t reserved;
+ grub_uint16_t len;
+ grub_uint32_t ptr;
+ } blocks[8];
+} GRUB_PACKED;
+
+struct grub_pxe_bangpxe *grub_pxe_pxenv;
+static grub_uint32_t pxe_rm_entry = 0;
+
+static struct grub_pxe_bangpxe *
+grub_pxe_scan (void)
+{
+ struct grub_bios_int_registers regs;
+ struct grub_pxenv *pxenv;
+ struct grub_pxe_bangpxe *bangpxe;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ regs.ebx = 0;
+ regs.ecx = 0;
+ regs.eax = 0x5650;
+ regs.es = 0;
+
+ grub_bios_interrupt (0x1a, &regs);
+
+ if ((regs.eax & 0xffff) != 0x564e)
+ return NULL;
+
+ pxenv = (struct grub_pxenv *) ((regs.es << 4) + (regs.ebx & 0xffff));
+ if (grub_memcmp (pxenv->signature, GRUB_PXE_SIGNATURE,
+ sizeof (pxenv->signature))
+ != 0)
+ return NULL;
+
+ if (pxenv->version < 0x201)
+ return NULL;
+
+ bangpxe = (void *) ((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4)
+ + (pxenv->pxe_ptr & 0xffff));
+
+ if (!bangpxe)
+ return NULL;
+
+ if (grub_memcmp (bangpxe->signature, GRUB_PXE_BANGPXE_SIGNATURE,
+ sizeof (bangpxe->signature)) != 0)
+ return NULL;
+
+ pxe_rm_entry = bangpxe->rm_entry;
+
+ return bangpxe;
+}
+
+static struct grub_net_buff *
+grub_pxe_recv (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ struct grub_pxe_undi_isr *isr;
+ static int in_progress = 0;
+ grub_uint8_t *ptr, *end;
+ struct grub_net_buff *buf;
+
+ isr = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+
+ if (!in_progress)
+ {
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_START;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ /* Normally according to the specification we should also check
+ that isr->func_flag != GRUB_PXE_ISR_OUT_OURS but unfortunately it
+ breaks on intel cards.
+ */
+ if (isr->status)
+ {
+ in_progress = 0;
+ return NULL;
+ }
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_PROCESS;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ }
+ else
+ {
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ }
+
+ while (isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE)
+ {
+ if (isr->status || isr->func_flag == GRUB_PXE_ISR_OUT_DONE)
+ {
+ in_progress = 0;
+ return NULL;
+ }
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ }
+
+ buf = grub_netbuff_alloc (isr->frame_len + 2);
+ if (!buf)
+ return NULL;
+ /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible
+ by 4. So that IP header is aligned on 4 bytes. */
+ if (grub_netbuff_reserve (buf, 2))
+ {
+ grub_netbuff_free (buf);
+ return NULL;
+ }
+ ptr = buf->data;
+ end = ptr + isr->frame_len;
+ grub_netbuff_put (buf, isr->frame_len);
+ grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len);
+ ptr += isr->buffer_len;
+ while (ptr < end)
+ {
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ if (isr->status || isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE)
+ {
+ in_progress = 1;
+ grub_netbuff_free (buf);
+ return NULL;
+ }
+
+ grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len);
+ ptr += isr->buffer_len;
+ }
+ in_progress = 1;
+
+ return buf;
+}
+
+static grub_err_t
+grub_pxe_send (struct grub_net_card *dev __attribute__ ((unused)),
+ struct grub_net_buff *pack)
+{
+ struct grub_pxe_undi_transmit *trans;
+ struct grub_pxe_undi_tbd *tbd;
+ char *buf;
+
+ trans = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_memset (trans, 0, sizeof (*trans));
+ tbd = (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 128);
+ grub_memset (tbd, 0, sizeof (*tbd));
+ buf = (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 256);
+ grub_memcpy (buf, pack->data, pack->tail - pack->data);
+
+ trans->tbd = SEGOFS ((grub_addr_t) tbd);
+ trans->protocol = 0;
+ tbd->len = pack->tail - pack->data;
+ tbd->buf = SEGOFS ((grub_addr_t) buf);
+
+ grub_pxe_call (GRUB_PXENV_UNDI_TRANSMIT, trans, pxe_rm_entry);
+ if (trans->status)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+ return 0;
+}
+
+static void
+grub_pxe_close (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ if (pxe_rm_entry)
+ grub_pxe_call (GRUB_PXENV_UNDI_CLOSE,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+}
+
+static grub_err_t
+grub_pxe_open (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ struct grub_pxe_undi_open *ou;
+ ou = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_memset (ou, 0, sizeof (*ou));
+ ou->pkt_filter = 4;
+ grub_pxe_call (GRUB_PXENV_UNDI_OPEN, ou, pxe_rm_entry);
+
+ if (ou->status)
+ return grub_error (GRUB_ERR_IO, "can't open UNDI");
+ return GRUB_ERR_NONE;
+}
+
+struct grub_net_card_driver grub_pxe_card_driver =
+{
+ .open = grub_pxe_open,
+ .close = grub_pxe_close,
+ .send = grub_pxe_send,
+ .recv = grub_pxe_recv
+};
+
+struct grub_net_card grub_pxe_card =
+{
+ .driver = &grub_pxe_card_driver,
+ .name = "pxe"
+};
+
+static grub_err_t
+grub_pxe_shutdown (int flags)
+{
+ if (flags & GRUB_LOADER_FLAG_PXE_NOT_UNLOAD)
+ return GRUB_ERR_NONE;
+ if (!pxe_rm_entry)
+ return GRUB_ERR_NONE;
+
+ grub_pxe_call (GRUB_PXENV_UNDI_CLOSE,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+ grub_pxe_call (GRUB_PXENV_UNDI_SHUTDOWN,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+ grub_pxe_call (GRUB_PXENV_UNLOAD_STACK,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+ pxe_rm_entry = 0;
+ grub_net_card_unregister (&grub_pxe_card);
+
+ return GRUB_ERR_NONE;
+}
+
+/* Nothing we can do. */
+static grub_err_t
+grub_pxe_restore (void)
+{
+ return GRUB_ERR_NONE;
+}
+
+void *
+grub_pxe_get_cached (grub_uint16_t type)
+{
+ struct grub_pxenv_get_cached_info ci;
+ ci.packet_type = type;
+ ci.buffer = 0;
+ ci.buffer_size = 0;
+ grub_pxe_call (GRUB_PXENV_GET_CACHED_INFO, &ci, pxe_rm_entry);
+ if (ci.status)
+ return 0;
+
+ return LINEAR (ci.buffer);
+}
+
+static void
+grub_pc_net_config_real (char **device, char **path)
+{
+ struct grub_net_bootp_packet *bp;
+
+ bp = grub_pxe_get_cached (GRUB_PXENV_PACKET_TYPE_DHCP_ACK);
+
+ if (!bp)
+ return;
+ grub_net_configure_by_dhcp_ack ("pxe", &grub_pxe_card, 0,
+ bp, GRUB_PXE_BOOTP_SIZE,
+ 1, device, path);
+
+}
+
+static struct grub_preboot *fini_hnd;
+
+GRUB_MOD_INIT(pxe)
+{
+ struct grub_pxe_bangpxe *pxenv;
+ struct grub_pxe_undi_info *ui;
+ unsigned i;
+
+ pxenv = grub_pxe_scan ();
+ if (! pxenv)
+ return;
+
+ ui = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_memset (ui, 0, sizeof (*ui));
+ grub_pxe_call (GRUB_PXENV_UNDI_GET_INFORMATION, ui, pxe_rm_entry);
+
+ grub_memcpy (grub_pxe_card.default_address.mac, ui->current_addr,
+ sizeof (grub_pxe_card.default_address.mac));
+ for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++)
+ if (grub_pxe_card.default_address.mac[i] != 0)
+ break;
+ if (i != sizeof (grub_pxe_card.default_address.mac))
+ {
+ for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++)
+ if (grub_pxe_card.default_address.mac[i] != 0xff)
+ break;
+ }
+ if (i == sizeof (grub_pxe_card.default_address.mac))
+ grub_memcpy (grub_pxe_card.default_address.mac, ui->permanent_addr,
+ sizeof (grub_pxe_card.default_address.mac));
+ grub_pxe_card.mtu = ui->mtu;
+
+ grub_pxe_card.default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+
+ grub_net_card_register (&grub_pxe_card);
+ grub_pc_net_config = grub_pc_net_config_real;
+ fini_hnd = grub_loader_register_preboot_hook (grub_pxe_shutdown,
+ grub_pxe_restore,
+ GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+}
+
+GRUB_MOD_FINI(pxe)
+{
+ grub_pc_net_config = 0;
+ grub_pxe_shutdown (0);
+ grub_loader_unregister_preboot_hook (fini_hnd);
+}