freebsd-dev/sys/boot/i386/libi386/pxe.c
Marcel Moolenaar a2a6914f06 1. Suppress output for the TFTP-based PXE loader, but leave it in
place for the NFS-based PXE loader. Information like rootpath
    or rootip aren't that useful for TFTP and the gateway IP is
    typically already printed by the firmware.
2.  Only set boot.nfsroot.* environment variables for NFS. This
    makes it possible for the OS to work either way by checking
    for the presence or absence of environment variables.
3.  Set boot.netif.server when using TFTP so that the OS can fetch
    files as well. A typical use case for this is network-based
    installations with the installation process implemented on
    top of FreeBSD.
4.  The pxelinux loader has a set of alternative names it tries
    for configuration files. Make it easier to do something
    similar in Forth by providing the IP address as a 32-bit hex
    number in the pxeboot.ip variable and the MAC address with
    dashes in the pxeboot.hwaddr environment variable.

Obtained from:	Juniper Networks, Inc.
2014-07-27 16:29:57 +00:00

731 lines
17 KiB
C

/*-
* Copyright (c) 2000 Alfred Perlstein <alfred@freebsd.org>
* Copyright (c) 2000 Paul Saab <ps@freebsd.org>
* Copyright (c) 2000 John Baldwin <jhb@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stand.h>
#include <string.h>
#include <stdarg.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <net.h>
#include <netif.h>
#include <nfsv2.h>
#include <iodesc.h>
#include <bootp.h>
#include <bootstrap.h>
#include "btxv86.h"
#include "pxe.h"
/*
* Allocate the PXE buffers statically instead of sticking grimy fingers into
* BTX's private data area. The scratch buffer is used to send information to
* the PXE BIOS, and the data buffer is used to receive data from the PXE BIOS.
*/
#define PXE_BUFFER_SIZE 0x2000
#define PXE_TFTP_BUFFER_SIZE 512
static char scratch_buffer[PXE_BUFFER_SIZE];
static char data_buffer[PXE_BUFFER_SIZE];
static pxenv_t *pxenv_p = NULL; /* PXENV+ */
static pxe_t *pxe_p = NULL; /* !PXE */
static BOOTPLAYER bootplayer; /* PXE Cached information. */
static int pxe_debug = 0;
static int pxe_sock = -1;
static int pxe_opens = 0;
void pxe_enable(void *pxeinfo);
static void (*pxe_call)(int func);
static void pxenv_call(int func);
static void bangpxe_call(int func);
static int pxe_init(void);
static int pxe_strategy(void *devdata, int flag, daddr_t dblk,
size_t size, char *buf, size_t *rsize);
static int pxe_open(struct open_file *f, ...);
static int pxe_close(struct open_file *f);
static void pxe_print(int verbose);
static void pxe_cleanup(void);
static void pxe_setnfshandle(char *rootpath);
static void pxe_perror(int error);
static int pxe_netif_match(struct netif *nif, void *machdep_hint);
static int pxe_netif_probe(struct netif *nif, void *machdep_hint);
static void pxe_netif_init(struct iodesc *desc, void *machdep_hint);
static int pxe_netif_get(struct iodesc *desc, void *pkt, size_t len,
time_t timeout);
static int pxe_netif_put(struct iodesc *desc, void *pkt, size_t len);
static void pxe_netif_end(struct netif *nif);
#ifdef OLD_NFSV2
int nfs_getrootfh(struct iodesc*, char*, u_char*);
#else
int nfs_getrootfh(struct iodesc*, char*, uint32_t*, u_char*);
#endif
extern struct netif_stats pxe_st[];
extern u_int16_t __bangpxeseg;
extern u_int16_t __bangpxeoff;
extern void __bangpxeentry(void);
extern u_int16_t __pxenvseg;
extern u_int16_t __pxenvoff;
extern void __pxenventry(void);
struct netif_dif pxe_ifs[] = {
/* dif_unit dif_nsel dif_stats dif_private */
{0, 1, &pxe_st[0], 0}
};
struct netif_stats pxe_st[NENTS(pxe_ifs)];
struct netif_driver pxenetif = {
"pxenet",
pxe_netif_match,
pxe_netif_probe,
pxe_netif_init,
pxe_netif_get,
pxe_netif_put,
pxe_netif_end,
pxe_ifs,
NENTS(pxe_ifs)
};
struct netif_driver *netif_drivers[] = {
&pxenetif,
NULL
};
struct devsw pxedisk = {
"pxe",
DEVT_NET,
pxe_init,
pxe_strategy,
pxe_open,
pxe_close,
noioctl,
pxe_print,
pxe_cleanup
};
/*
* This function is called by the loader to enable PXE support if we
* are booted by PXE. The passed in pointer is a pointer to the
* PXENV+ structure.
*/
void
pxe_enable(void *pxeinfo)
{
pxenv_p = (pxenv_t *)pxeinfo;
pxe_p = (pxe_t *)PTOV(pxenv_p->PXEPtr.segment * 16 +
pxenv_p->PXEPtr.offset);
pxe_call = NULL;
}
/*
* return true if pxe structures are found/initialized,
* also figures out our IP information via the pxe cached info struct
*/
static int
pxe_init(void)
{
t_PXENV_GET_CACHED_INFO *gci_p;
int counter;
uint8_t checksum;
uint8_t *checkptr;
if(pxenv_p == NULL)
return (0);
/* look for "PXENV+" */
if (bcmp((void *)pxenv_p->Signature, S_SIZE("PXENV+"))) {
pxenv_p = NULL;
return (0);
}
/* make sure the size is something we can handle */
if (pxenv_p->Length > sizeof(*pxenv_p)) {
printf("PXENV+ structure too large, ignoring\n");
pxenv_p = NULL;
return (0);
}
/*
* do byte checksum:
* add up each byte in the structure, the total should be 0
*/
checksum = 0;
checkptr = (uint8_t *) pxenv_p;
for (counter = 0; counter < pxenv_p->Length; counter++)
checksum += *checkptr++;
if (checksum != 0) {
printf("PXENV+ structure failed checksum, ignoring\n");
pxenv_p = NULL;
return (0);
}
/*
* PXENV+ passed, so use that if !PXE is not available or
* the checksum fails.
*/
pxe_call = pxenv_call;
if (pxenv_p->Version >= 0x0200) {
for (;;) {
if (bcmp((void *)pxe_p->Signature, S_SIZE("!PXE"))) {
pxe_p = NULL;
break;
}
checksum = 0;
checkptr = (uint8_t *)pxe_p;
for (counter = 0; counter < pxe_p->StructLength;
counter++)
checksum += *checkptr++;
if (checksum != 0) {
pxe_p = NULL;
break;
}
pxe_call = bangpxe_call;
break;
}
}
printf("\nPXE version %d.%d, real mode entry point ",
(uint8_t) (pxenv_p->Version >> 8),
(uint8_t) (pxenv_p->Version & 0xFF));
if (pxe_call == bangpxe_call)
printf("@%04x:%04x\n",
pxe_p->EntryPointSP.segment,
pxe_p->EntryPointSP.offset);
else
printf("@%04x:%04x\n",
pxenv_p->RMEntry.segment, pxenv_p->RMEntry.offset);
gci_p = (t_PXENV_GET_CACHED_INFO *) scratch_buffer;
bzero(gci_p, sizeof(*gci_p));
gci_p->PacketType = PXENV_PACKET_TYPE_BINL_REPLY;
pxe_call(PXENV_GET_CACHED_INFO);
if (gci_p->Status != 0) {
pxe_perror(gci_p->Status);
pxe_p = NULL;
return (0);
}
bcopy(PTOV((gci_p->Buffer.segment << 4) + gci_p->Buffer.offset),
&bootplayer, gci_p->BufferSize);
return (1);
}
static int
pxe_strategy(void *devdata, int flag, daddr_t dblk, size_t size,
char *buf, size_t *rsize)
{
return (EIO);
}
static int
pxe_open(struct open_file *f, ...)
{
va_list args;
char *devname; /* Device part of file name (or NULL). */
char temp[FNAME_SIZE];
int error = 0;
int i;
va_start(args, f);
devname = va_arg(args, char*);
va_end(args);
/* On first open, do netif open, mount, etc. */
if (pxe_opens == 0) {
/* Find network interface. */
if (pxe_sock < 0) {
pxe_sock = netif_open(devname);
if (pxe_sock < 0) {
printf("pxe_open: netif_open() failed\n");
return (ENXIO);
}
if (pxe_debug)
printf("pxe_open: netif_open() succeeded\n");
}
if (rootip.s_addr == 0) {
/*
* Do a bootp/dhcp request to find out where our
* NFS/TFTP server is. Even if we dont get back
* the proper information, fall back to the server
* which brought us to life and a default rootpath.
*/
bootp(pxe_sock, BOOTP_PXE);
if (rootip.s_addr == 0)
rootip.s_addr = bootplayer.sip;
if (!rootpath[0])
strcpy(rootpath, PXENFSROOTPATH);
for (i = 0; rootpath[i] != '\0' && i < FNAME_SIZE; i++)
if (rootpath[i] == ':')
break;
if (i && i != FNAME_SIZE && rootpath[i] == ':') {
rootpath[i++] = '\0';
if (inet_addr(&rootpath[0]) != INADDR_NONE)
rootip.s_addr = inet_addr(&rootpath[0]);
bcopy(&rootpath[i], &temp[0], strlen(&rootpath[i])+1);
bcopy(&temp[0], &rootpath[0], strlen(&rootpath[i])+1);
}
setenv("boot.netif.ip", inet_ntoa(myip), 1);
setenv("boot.netif.netmask", intoa(netmask), 1);
setenv("boot.netif.gateway", inet_ntoa(gateip), 1);
if (bootplayer.Hardware == ETHER_TYPE) {
sprintf(temp, "%6D", bootplayer.CAddr, ":");
setenv("boot.netif.hwaddr", temp, 1);
}
#ifdef LOADER_NFS_SUPPORT
printf("pxe_open: server addr: %s\n", inet_ntoa(rootip));
printf("pxe_open: server path: %s\n", rootpath);
printf("pxe_open: gateway ip: %s\n", inet_ntoa(gateip));
setenv("boot.nfsroot.server", inet_ntoa(rootip), 1);
setenv("boot.nfsroot.path", rootpath, 1);
#else
setenv("boot.netif.server", inet_ntoa(rootip), 1);
#endif
setenv("dhcp.host-name", hostname, 1);
sprintf(temp, "%08X", ntohl(myip.s_addr));
setenv("pxeboot.ip", temp, 1);
if (bootplayer.Hardware == ETHER_TYPE) {
sprintf(temp, "%6D", bootplayer.CAddr, "-");
setenv("pxeboot.hwaddr", temp, 1);
}
}
}
pxe_opens++;
f->f_devdata = &pxe_sock;
return (error);
}
static int
pxe_close(struct open_file *f)
{
#ifdef PXE_DEBUG
if (pxe_debug)
printf("pxe_close: opens=%d\n", pxe_opens);
#endif
/* On last close, do netif close, etc. */
f->f_devdata = NULL;
/* Extra close call? */
if (pxe_opens <= 0)
return (0);
pxe_opens--;
/* Not last close? */
if (pxe_opens > 0)
return(0);
#ifdef LOADER_NFS_SUPPORT
/* get an NFS filehandle for our root filesystem */
pxe_setnfshandle(rootpath);
#endif
if (pxe_sock >= 0) {
#ifdef PXE_DEBUG
if (pxe_debug)
printf("pxe_close: calling netif_close()\n");
#endif
netif_close(pxe_sock);
pxe_sock = -1;
}
return (0);
}
static void
pxe_print(int verbose)
{
if (pxe_call == NULL)
return;
printf(" pxe0: %s:%s\n", inet_ntoa(rootip), rootpath);
}
static void
pxe_cleanup(void)
{
#ifdef PXE_DEBUG
t_PXENV_UNLOAD_STACK *unload_stack_p =
(t_PXENV_UNLOAD_STACK *)scratch_buffer;
t_PXENV_UNDI_SHUTDOWN *undi_shutdown_p =
(t_PXENV_UNDI_SHUTDOWN *)scratch_buffer;
#endif
if (pxe_call == NULL)
return;
pxe_call(PXENV_UNDI_SHUTDOWN);
#ifdef PXE_DEBUG
if (pxe_debug && undi_shutdown_p->Status != 0)
printf("pxe_cleanup: UNDI_SHUTDOWN failed %x\n",
undi_shutdown_p->Status);
#endif
pxe_call(PXENV_UNLOAD_STACK);
#ifdef PXE_DEBUG
if (pxe_debug && unload_stack_p->Status != 0)
printf("pxe_cleanup: UNLOAD_STACK failed %x\n",
unload_stack_p->Status);
#endif
}
void
pxe_perror(int err)
{
return;
}
#ifdef LOADER_NFS_SUPPORT
/*
* Reach inside the libstand NFS code and dig out an NFS handle
* for the root filesystem.
*/
#ifdef OLD_NFSV2
struct nfs_iodesc {
struct iodesc *iodesc;
off_t off;
u_char fh[NFS_FHSIZE];
/* structure truncated here */
};
extern struct nfs_iodesc nfs_root_node;
extern int rpc_port;
static void
pxe_rpcmountcall()
{
struct iodesc *d;
int error;
if (!(d = socktodesc(pxe_sock)))
return;
d->myport = htons(--rpc_port);
d->destip = rootip;
if ((error = nfs_getrootfh(d, rootpath, nfs_root_node.fh)) != 0)
printf("NFS MOUNT RPC error: %d\n", error);
nfs_root_node.iodesc = d;
}
static void
pxe_setnfshandle(char *rootpath)
{
int i;
u_char *fh;
char buf[2 * NFS_FHSIZE + 3], *cp;
/*
* If NFS files were never opened, we need to do mount call
* ourselves. Use nfs_root_node.iodesc as flag indicating
* previous NFS usage.
*/
if (nfs_root_node.iodesc == NULL)
pxe_rpcmountcall();
fh = &nfs_root_node.fh[0];
buf[0] = 'X';
cp = &buf[1];
for (i = 0; i < NFS_FHSIZE; i++, cp += 2)
sprintf(cp, "%02x", fh[i]);
sprintf(cp, "X");
setenv("boot.nfsroot.nfshandle", buf, 1);
}
#else /* !OLD_NFSV2 */
#define NFS_V3MAXFHSIZE 64
struct nfs_iodesc {
struct iodesc *iodesc;
off_t off;
uint32_t fhsize;
u_char fh[NFS_V3MAXFHSIZE];
/* structure truncated */
};
extern struct nfs_iodesc nfs_root_node;
extern int rpc_port;
static void
pxe_rpcmountcall()
{
struct iodesc *d;
int error;
if (!(d = socktodesc(pxe_sock)))
return;
d->myport = htons(--rpc_port);
d->destip = rootip;
if ((error = nfs_getrootfh(d, rootpath, &nfs_root_node.fhsize,
nfs_root_node.fh)) != 0) {
printf("NFS MOUNT RPC error: %d\n", error);
nfs_root_node.fhsize = 0;
}
nfs_root_node.iodesc = d;
}
static void
pxe_setnfshandle(char *rootpath)
{
int i;
u_char *fh;
char buf[2 * NFS_V3MAXFHSIZE + 3], *cp;
/*
* If NFS files were never opened, we need to do mount call
* ourselves. Use nfs_root_node.iodesc as flag indicating
* previous NFS usage.
*/
if (nfs_root_node.iodesc == NULL)
pxe_rpcmountcall();
fh = &nfs_root_node.fh[0];
buf[0] = 'X';
cp = &buf[1];
for (i = 0; i < nfs_root_node.fhsize; i++, cp += 2)
sprintf(cp, "%02x", fh[i]);
sprintf(cp, "X");
setenv("boot.nfsroot.nfshandle", buf, 1);
sprintf(buf, "%d", nfs_root_node.fhsize);
setenv("boot.nfsroot.nfshandlelen", buf, 1);
}
#endif /* OLD_NFSV2 */
#endif /* LOADER_NFS_SUPPORT */
void
pxenv_call(int func)
{
#ifdef PXE_DEBUG
if (pxe_debug)
printf("pxenv_call %x\n", func);
#endif
bzero(&v86, sizeof(v86));
bzero(data_buffer, sizeof(data_buffer));
__pxenvseg = pxenv_p->RMEntry.segment;
__pxenvoff = pxenv_p->RMEntry.offset;
v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS;
v86.es = VTOPSEG(scratch_buffer);
v86.edi = VTOPOFF(scratch_buffer);
v86.addr = (VTOPSEG(__pxenventry) << 16) | VTOPOFF(__pxenventry);
v86.ebx = func;
v86int();
v86.ctl = V86_FLAGS;
}
void
bangpxe_call(int func)
{
#ifdef PXE_DEBUG
if (pxe_debug)
printf("bangpxe_call %x\n", func);
#endif
bzero(&v86, sizeof(v86));
bzero(data_buffer, sizeof(data_buffer));
__bangpxeseg = pxe_p->EntryPointSP.segment;
__bangpxeoff = pxe_p->EntryPointSP.offset;
v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS;
v86.edx = VTOPSEG(scratch_buffer);
v86.eax = VTOPOFF(scratch_buffer);
v86.addr = (VTOPSEG(__bangpxeentry) << 16) | VTOPOFF(__bangpxeentry);
v86.ebx = func;
v86int();
v86.ctl = V86_FLAGS;
}
time_t
getsecs()
{
time_t n = 0;
time(&n);
return n;
}
static int
pxe_netif_match(struct netif *nif, void *machdep_hint)
{
return 1;
}
static int
pxe_netif_probe(struct netif *nif, void *machdep_hint)
{
t_PXENV_UDP_OPEN *udpopen_p = (t_PXENV_UDP_OPEN *)scratch_buffer;
if (pxe_call == NULL)
return -1;
bzero(udpopen_p, sizeof(*udpopen_p));
udpopen_p->src_ip = bootplayer.yip;
pxe_call(PXENV_UDP_OPEN);
if (udpopen_p->status != 0) {
printf("pxe_netif_probe: failed %x\n", udpopen_p->status);
return -1;
}
return 0;
}
static void
pxe_netif_end(struct netif *nif)
{
t_PXENV_UDP_CLOSE *udpclose_p = (t_PXENV_UDP_CLOSE *)scratch_buffer;
bzero(udpclose_p, sizeof(*udpclose_p));
pxe_call(PXENV_UDP_CLOSE);
if (udpclose_p->status != 0)
printf("pxe_end failed %x\n", udpclose_p->status);
}
static void
pxe_netif_init(struct iodesc *desc, void *machdep_hint)
{
int i;
for (i = 0; i < 6; ++i)
desc->myea[i] = bootplayer.CAddr[i];
desc->xid = bootplayer.ident;
}
static int
pxe_netif_get(struct iodesc *desc, void *pkt, size_t len, time_t timeout)
{
return len;
}
static int
pxe_netif_put(struct iodesc *desc, void *pkt, size_t len)
{
return len;
}
ssize_t
sendudp(struct iodesc *h, void *pkt, size_t len)
{
t_PXENV_UDP_WRITE *udpwrite_p = (t_PXENV_UDP_WRITE *)scratch_buffer;
bzero(udpwrite_p, sizeof(*udpwrite_p));
udpwrite_p->ip = h->destip.s_addr;
udpwrite_p->dst_port = h->destport;
udpwrite_p->src_port = h->myport;
udpwrite_p->buffer_size = len;
udpwrite_p->buffer.segment = VTOPSEG(pkt);
udpwrite_p->buffer.offset = VTOPOFF(pkt);
if (netmask == 0 || SAMENET(myip, h->destip, netmask))
udpwrite_p->gw = 0;
else
udpwrite_p->gw = gateip.s_addr;
pxe_call(PXENV_UDP_WRITE);
#if 0
/* XXX - I dont know why we need this. */
delay(1000);
#endif
if (udpwrite_p->status != 0) {
/* XXX: This happens a lot. It shouldn't. */
if (udpwrite_p->status != 1)
printf("sendudp failed %x\n", udpwrite_p->status);
return -1;
}
return len;
}
ssize_t
readudp(struct iodesc *h, void *pkt, size_t len, time_t timeout)
{
t_PXENV_UDP_READ *udpread_p = (t_PXENV_UDP_READ *)scratch_buffer;
struct udphdr *uh = NULL;
uh = (struct udphdr *) pkt - 1;
bzero(udpread_p, sizeof(*udpread_p));
udpread_p->dest_ip = h->myip.s_addr;
udpread_p->d_port = h->myport;
udpread_p->buffer_size = len;
udpread_p->buffer.segment = VTOPSEG(data_buffer);
udpread_p->buffer.offset = VTOPOFF(data_buffer);
pxe_call(PXENV_UDP_READ);
#if 0
/* XXX - I dont know why we need this. */
delay(1000);
#endif
if (udpread_p->status != 0) {
/* XXX: This happens a lot. It shouldn't. */
if (udpread_p->status != 1)
printf("readudp failed %x\n", udpread_p->status);
return -1;
}
bcopy(data_buffer, pkt, udpread_p->buffer_size);
uh->uh_sport = udpread_p->s_port;
return udpread_p->buffer_size;
}
char *
pxe_default_rc(void)
{
char *rc;
size_t count, rcsz;
/* XXX It may not be a good idea to modify the PXE boot file. */
rc = (char *)bootplayer.bootfile;
rcsz = sizeof(bootplayer.bootfile);
/* Ignore how we define rc and rcsz above -- it can change. */
if (rcsz < 6)
return (NULL);
if (*rc == '\0') {
strncpy(rc, "pxeboot", rcsz);
rc[rcsz - 1] = '\0';
}
count = strlen(rc);
strncat(rc, ".4th", rcsz - count - 1);
printf("PXE: loading Forth from %s\n", rc);
return (rc);
}