root/stand/boot/pc32/libi386/pxe.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.
 *
 * $FreeBSD: src/sys/boot/i386/libi386/pxe.c,v 1.20 2003/08/25 23:28:31 obrien Exp $
 */

#include <sys/param.h>
#include <stand.h>
#include <string.h>
#include <stdarg.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/ip.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;
static int      bugged_bios_pxe = 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);

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[1])
                        strcpy(rootpath, PXENFSROOTPATH);

                for (i = 0; i < FNAME_SIZE && rootpath[i] != '\0'; 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);
                }
                printf("pxe_open: ip address : %s\n", inet_ntoa(myip));
                printf("pxe_open: ip netmask : %s\n", intoa(netmask));
                printf("pxe_open: nfs root mount: %s:%s\n", inet_ntoa(rootip), rootpath);
                printf("pxe_open: gateway ip:  %s\n", inet_ntoa(gateip));

                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)
                        setenv("boot.netif.hwaddr", ether_sprintf(bootplayer.CAddr), 1);

                setenv("boot.nfsroot.server", inet_ntoa(rootip), 1);
                setenv("boot.nfsroot.path", rootpath, 1);

                if (bootplayer.yip != INADDR_ANY &&
                    bootplayer.yip != myip.s_addr) {
                        printf("Warning: PXE negotiated a different IP "
                               "in the preloader\n");
                        bugged_bios_pxe = 1;
                }
        }
    }
    pxe_opens++;
    devreplace(f, &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);

    /* get an NFS filehandle for our root filesystem */
    pxe_setnfshandle(rootpath);

    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) {
                if (*bootplayer.Sname == '\0') {
                        printf("      "IP_STR":%s\n",
                               IP_ARGS(htonl(bootplayer.sip)),
                               bootplayer.bootfile);
                } else {
                        printf("      %s:%s\n", bootplayer.Sname,
                               bootplayer.bootfile);
                }
        }

        return;
}

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;
}

/* To prevent LTO warnings. Must match stand/lib/nfs.c struct. */
struct nfsv2_fattrs {
        n_long  fa_type;
        n_long  fa_mode;
        n_long  fa_nlink;
        n_long  fa_uid;
        n_long  fa_gid;
        n_long  fa_size;
        n_long  fa_blocksize;
        n_long  fa_rdev;
        n_long  fa_blocks;
        n_long  fa_fsid;
        n_long  fa_fileid;
        struct nfsv2_time fa_atime;
        struct nfsv2_time fa_mtime;
        struct nfsv2_time fa_ctime;
};

/*
 * Reach inside the libstand NFS code and dig out an NFS handle
 * for the root filesystem.  If there is no nfs handle but a NFS root
 * path was dynamically requested (not just as a default), then try
 * to get the handle.  This occurs if we are compiled for TFTP operation
 * but still want to pass an NFS root to the kernel.
 */
struct nfs_iodesc {
        struct  iodesc  *iodesc;
        off_t   off;
        u_char  fh[NFS_FHSIZE];
        /* structure truncated here */
        struct nfsv2_fattrs unused;     /* unused */
};
extern struct   nfs_iodesc nfs_root_node;

static void
pxe_setnfshandle(char *rootpath)
{
        int     i;
        u_char  *fh;
        char    buf[2 * NFS_FHSIZE + 3], *cp;

        fh = &nfs_root_node.fh[0];

        /*
         * If no file handle exists but a root path was dynamically
         * requested, try to get a good handle.
         */
        for (i = 0; i < NFS_FHSIZE; ++i) {
                if (fh[i])
                        break;
        }
        if (i != NFS_FHSIZE) {
                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);
        }
}

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(void)
{
        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;
        struct ip *ip;
        
        uh = (struct udphdr *) pkt - 1;
        ip = (struct ip *)uh - 1;
again:
        bzero(udpread_p, sizeof(*udpread_p));

        /*
         * Bugged BIOSes (e.g. Gigabyte H97N-WIFI) can wind up asking for
         * a different IP than we negotiated, then using that IP instead
         * of the one we specified in the udpopen().
         */
        if (bugged_bios_pxe)
                udpread_p->dest_ip = INADDR_ANY;
        else
                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 (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;
        }

        /*
         * If the BIOS is bugged in this manner we were forced to allow
         * any address in dest_ip and have to filter the packets ourselves.
         * The bugged BIOS used the wrong IP in the udpwrite (it used the
         * previously negotiated bootplayer.yip IP).  So make sure the IP
         * is either that one or the one we negotiated and specified in the
         * udpopen ourselves.
         */
        if (bugged_bios_pxe) {
                if (udpread_p->dest_ip != h->myip.s_addr &&
                    udpread_p->dest_ip != bootplayer.yip &&
                    udpread_p->dest_ip != INADDR_ANY) {
                        goto again;
                }
        }

        bcopy(data_buffer, pkt, udpread_p->buffer_size);
        uh->uh_sport = udpread_p->s_port;
        ip->ip_src.s_addr = udpread_p->src_ip;
        return udpread_p->buffer_size;
}