blob: 343ff98977b3ee69288c054aaf48777e347344d1 [file] [log] [blame] [raw]
/*
* PXE file system for GRUB
*
* Copyright (C) 2007 Bean (bean123@126.com)
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef FSYS_PXE
#include "shared.h"
#include "filesys.h"
#include "pxe.h"
#include "etherboot.h"
#ifdef GRUB_UTIL
int pxe_mount(void) { return 0; }
unsigned long pxe_read (char *buf, unsigned long len) { return -1; }
int pxe_dir (char *dirname) { return 0; }
void pxe_close (void) {}
#else
#ifndef TFTP_PORT
#define TFTP_PORT 69
#endif
#define PXE_MIN_BLKSIZE 512
#define PXE_MAX_BLKSIZE 1432
#define DOT_SIZE 1048576
#define PXE_BUF FSYS_BUF
#define PXE_BUFLEN FSYS_BUFLEN
//#define PXE_DEBUG 1
unsigned long pxe_entry,pxe_blksize=PXE_MAX_BLKSIZE;
unsigned short pxe_basemem,pxe_freemem;
static IP4 pxe_yip,pxe_sip,pxe_gip;
static UINT8 pxe_mac_len,pxe_mac_type,pxe_tftp_opened;
static MAC_ADDR pxe_mac;
static unsigned long pxe_saved_pos,pxe_cur_ofs,pxe_read_ofs,pxe_keep;
static PXENV_TFTP_OPEN_t pxe_tftp_open;
static char *pxe_tftp_name;
extern unsigned long ROM_int15;
extern struct drive_map_slot bios_drive_map[DRIVE_MAP_SIZE + 1];
static char* pxe_outhex(char* pc,unsigned char c)
{
int i;
pc+=2;
for (i=1;i<=2;i++)
{
unsigned char t;
t=c & 0xF;
if (t>=10)
t+='A'-10;
else
t+='0';
*(pc-i)=t;
c=c>>4;
}
return pc;
}
void pxe_detect(void)
{
PXENV_GET_CACHED_INFO_t get_cached_info;
BOOTPLAYER *bp;
unsigned long tmp;
char *pc;
int i,ret;
if (! pxe_scan())
return;
pxe_basemem=*((unsigned short*)0x413);
get_cached_info.PacketType=PXENV_PACKET_TYPE_DHCP_ACK;
get_cached_info.Buffer=get_cached_info.BufferSize=0;
pxe_call(PXENV_GET_CACHED_INFO,&get_cached_info);
if (get_cached_info.Status)
return;
bp=LINEAR(get_cached_info.Buffer);
pxe_yip=bp->yip;
pxe_sip=bp->sip;
pxe_gip=bp->gip;
pxe_mac_type=bp->Hardware;
pxe_mac_len=bp->Hardlen;
grub_memmove(&pxe_mac,&bp->CAddr,pxe_mac_len);
get_cached_info.PacketType=PXENV_PACKET_TYPE_CACHED_REPLY;
get_cached_info.Buffer=get_cached_info.BufferSize=0;
pxe_call(PXENV_GET_CACHED_INFO,&get_cached_info);
if (get_cached_info.Status)
return;
bp=LINEAR(get_cached_info.Buffer);
if (bp->bootfile[0])
{
int n;
n=grub_strlen((char*)bp->bootfile)-1;
grub_strcpy((char*)&pxe_tftp_open.FileName,(char*)bp->bootfile);
while ((n>=0) && (pxe_tftp_open.FileName[n]!='/'))
n--;
if (n<0)
n=0;
pxe_tftp_name=(char*)&pxe_tftp_open.FileName[n];
}
else
pxe_tftp_name=(char*)&pxe_tftp_open.FileName[0];
pxe_tftp_opened=0;
ret=0;
grub_strcpy(pxe_tftp_name,"/menu.lst/");
pc=pxe_tftp_name+10;
pc=pxe_outhex(pc,pxe_mac_type);
for (i=0;i<pxe_mac_len;i++)
{
*(pc++)='-';
pc=pxe_outhex(pc,pxe_mac[i]);
}
*pc=0;
grub_printf("\n%s\n",pxe_tftp_open.FileName);
if (pxe_dir(pxe_tftp_name))
{
ret=1;
goto done;
}
pc=pxe_tftp_name+10;
tmp=pxe_yip;
for (i=0;i<4;i++)
{
pc=pxe_outhex(pc,tmp & 0xFF);
tmp >>=8;
}
*pc=0;
do
{
grub_printf("%s\n",pxe_tftp_open.FileName);
if (pxe_dir(pxe_tftp_name))
{
ret=1;
goto done;
}
*(--pc)=0;
} while (pc>pxe_tftp_name+10);
grub_strcpy(pc,"default");
grub_printf("%s\n",pxe_tftp_open.FileName);
ret=pxe_dir(pxe_tftp_name);
done:
if (ret)
{
unsigned long nr;
nr=4096-1;
if (nr>filemax)
nr=filemax;
nr=pxe_read((char*)0x800,nr);
if (nr!=PXE_ERR_LEN)
{
*(char*)(0x800+nr)=0;
if (preset_menu!=(char*)0x800)
preset_menu=(char*)0x800;
if (nr<filemax)
{
grub_printf("Boot menu truncated\n");
pxe_read(NULL,filemax-nr);
}
}
pxe_close();
}
//getkey();
}
#if PXE_TFTP_MODE
static int pxe_reopen(void)
{
pxe_close();
pxe_call(PXENV_TFTP_OPEN, &pxe_tftp_open);
if (pxe_tftp_open.Status)
return 0;
pxe_blksize=pxe_tftp_open.PacketSize;
pxe_tftp_opened=1;
pxe_saved_pos=pxe_cur_ofs=pxe_read_ofs=0;
return 1;
}
static int pxe_open(char* name)
{
PXENV_TFTP_GET_FSIZE_t *tftp_get_fsize;
tftp_get_fsize=(void*)&pxe_tftp_open;
tftp_get_fsize->ServerIPAddress=pxe_sip;
tftp_get_fsize->GatewayIPAddress=pxe_gip;
if (name!=pxe_tftp_name)
grub_strcpy(pxe_tftp_name,name);
pxe_call(PXENV_TFTP_GET_FSIZE,tftp_get_fsize);
if (tftp_get_fsize->Status)
return 0;
filemax=tftp_get_fsize->FileSize;
pxe_tftp_open.TFTPPort=htons(TFTP_PORT);
pxe_tftp_open.PacketSize=pxe_blksize;
return pxe_reopen();
}
void pxe_close(void)
{
if (pxe_tftp_opened)
{
PXENV_TFTP_CLOSE_t tftp_close;
pxe_call(PXENV_TFTP_CLOSE,&tftp_close);
pxe_tftp_opened=0;
}
}
#if PXE_FAST_READ
/* Read num packets , BUF must be segment aligned*/
static unsigned long pxe_read_blk(unsigned long buf,int num)
{
PXENV_TFTP_READ_t tftp_read;
unsigned long ofs;
tftp_read.Buffer=SEGOFS(buf);
ofs=tftp_read.Buffer & 0xFFFF;
pxe_fast_read(&tftp_read, num);
return (tftp_read.Status)?PXE_ERR_LEN:((tftp_read.Buffer & 0xFFFF)-ofs);
}
#else
static unsigned long pxe_read_blk(unsigned long buf,int num)
{
PXENV_TFTP_READ_t tftp_read;
unsigned long ofs;
tftp_read.Buffer=SEGOFS(buf);
ofs=tftp_read.Buffer & 0xFFFF;
while (num>0)
{
pxe_call(PXENV_TFTP_READ, &tftp_read);
if (tftp_read.Status)
return PXE_ERR_LEN;
tftp_read.Buffer+=tftp_read.BufferSize;
if (tftp_read.BufferSize<pxe_blksize)
break;
num--;
}
return (tftp_read.Buffer & 0xFFFF) - ofs;
}
#endif
#else
#endif
static unsigned long pxe_read_len (char* buf, unsigned long len)
{
unsigned long old_ofs,sz;
if (len==0)
return 0;
sz=0;
old_ofs=pxe_cur_ofs;
pxe_cur_ofs+=len;
if (pxe_cur_ofs>pxe_read_ofs)
{
unsigned long nb,nr;
long nb_del,nb_pos;
sz=(pxe_read_ofs-old_ofs);
if ((buf) && (sz))
{
grub_memmove(buf,(char*)(PXE_BUF+old_ofs),sz);
buf+=sz;
}
pxe_cur_ofs-=pxe_read_ofs;
nb=pxe_cur_ofs / pxe_blksize;
nb_del=DOT_SIZE / pxe_blksize;
if (nb_del>nb)
{
nb_del=0;
nb_pos=-1;
}
else
nb_pos=nb-nb_del;
pxe_cur_ofs-=pxe_blksize*nb;
if (pxe_read_ofs+pxe_blksize>PXE_BUFLEN)
pxe_read_ofs=0;
while (nb>0)
{
unsigned long nn;
nn=(PXE_BUFLEN - pxe_read_ofs) / pxe_blksize;
if (nn>nb)
nn=nb;
nr=pxe_read_blk(PXE_BUF+pxe_read_ofs,nn);
if (nr==PXE_ERR_LEN)
return nr;
sz+=nr;
if (buf)
{
grub_memmove(buf,(char*)(PXE_BUF+pxe_read_ofs),nr);
buf+=nr;
}
if (nr<nn*pxe_blksize)
{
pxe_read_ofs+=nr;
pxe_cur_ofs=pxe_read_ofs;
return sz;
}
nb-=nn;
if (nb)
pxe_read_ofs=0;
else
pxe_read_ofs+=nr;
if ((long)nb<=nb_pos)
{
grub_putchar('.');
nb_pos-=nb_del;
}
}
if (nb_del)
{
grub_putchar('\r');
grub_putchar('\n');
}
if (pxe_cur_ofs)
{
if (pxe_read_ofs+pxe_blksize>PXE_BUFLEN)
pxe_read_ofs=0;
nr=pxe_read_blk(PXE_BUF+pxe_read_ofs,1);
if (nr==PXE_ERR_LEN)
return nr;
if (pxe_cur_ofs>nr)
pxe_cur_ofs=nr;
sz+=pxe_cur_ofs;
if (buf)
grub_memmove(buf,(char*)(PXE_BUF+pxe_read_ofs),pxe_cur_ofs);
pxe_cur_ofs+=pxe_read_ofs;
pxe_read_ofs+=nr;
}
else
pxe_cur_ofs=pxe_read_ofs;
}
else
{
sz+=len;
if (buf)
grub_memmove(buf,(char*)PXE_BUF+old_ofs,len);
}
return sz;
}
/* Mount the network drive. If the drive is ready, return 1, otherwise
return 0. */
int pxe_mount(void)
{
if (current_drive != PXE_DRIVE)
return 0;
return 1;
}
/* Read up to SIZE bytes, returned in ADDR. */
unsigned long pxe_read (char *buf, unsigned long len)
{
unsigned long nr;
if (! pxe_tftp_opened)
return PXE_ERR_LEN;
if (pxe_saved_pos!=filepos)
{
if ((filepos<pxe_saved_pos) && (filepos+pxe_cur_ofs>=pxe_saved_pos))
pxe_cur_ofs-=pxe_saved_pos-filepos;
else
{
if (pxe_saved_pos>filepos)
{
//grub_printf("reopen\n");
if (! pxe_reopen())
return PXE_ERR_LEN;
}
nr=pxe_read_len(NULL, filepos-pxe_saved_pos);
if ((nr==PXE_ERR_LEN) || (pxe_saved_pos+nr!=filepos))
return PXE_ERR_LEN;
}
pxe_saved_pos=filepos;
}
nr=pxe_read_len(buf,len);
if (nr!=PXE_ERR_LEN)
{
filepos+=nr;
pxe_saved_pos=filepos;
}
return nr;
}
/* Check if the file DIRNAME really exists. Get the size and save it in
FILEMAX. return 1 if succeed, 0 if fail. */
int pxe_dir (char *dirname)
{
int ret,ch;
if (print_possibilities)
return 1;
pxe_close();
ret=1;
ch=nul_terminate(dirname);
if (! pxe_open(dirname))
{
errnum=ERR_FILE_NOT_FOUND;
ret=0;
}
dirname[grub_strlen(dirname)]=ch;
return ret;
}
void pxe_unload(void)
{
PXENV_UNLOAD_STACK_t unload;
unsigned char code[]={PXENV_UNDI_SHUTDOWN,PXENV_UNLOAD_STACK,PXENV_STOP_UNDI,0};
int i,h;
if (! pxe_entry)
return;
pxe_close();
if (pxe_keep)
return;
h=unset_int13_handler(1);
if (! h)
unset_int13_handler(0);
i=0;
while (code[i])
{
grub_memset(&unload,0,sizeof(unload));
pxe_call(code[i],&unload);
if (unload.Status)
{
grub_printf("PXE unload fails: %d\n",unload.Status);
goto quit;
}
i++;
}
if (*((unsigned short*)0x413)==pxe_basemem)
*((unsigned short*)0x413)=pxe_freemem;
pxe_entry=0;
ROM_int15=*((unsigned long*)0x54);
grub_printf("PXE stack unloaded\n");
quit:
if (! h)
set_int13_handler(bios_drive_map);
}
static void print_ip(IP4 ip)
{
int i;
for (i=0;i<3;i++)
{
grub_printf("%d.",ip & 0xFF);
ip>>=8;
}
grub_printf("%d",ip);
}
int pxe_func(char* arg,int flags)
{
if (! pxe_entry)
{
grub_printf("No PXE stack\n");
errnum = ERR_BAD_ARGUMENT;
return 1;
}
if (*arg==0)
{
char buf[4],*pc;
int i;
pxe_tftp_name[0]='/';
pxe_tftp_name[1]=0;
grub_printf("blksize : %d\n",pxe_blksize);
grub_printf("basedir : %s\n",pxe_tftp_open.FileName);
grub_printf("client ip : ");
print_ip(pxe_yip);
grub_printf("\nserver ip : ");
print_ip(pxe_sip);
grub_printf("\ngateway ip : ");
print_ip(pxe_gip);
grub_printf("\nmac : ");
for (i=0;i<pxe_mac_len;i++)
{
pc=buf;
pc=pxe_outhex(pc,pxe_mac[i]);
*pc=0;
grub_printf("%s%c",buf,(i==pxe_mac_len-1)?'\n':'-');
}
}
else if (grub_memcmp(arg, "blksize", sizeof("blksize")-1)==0)
{
int val;
arg=skip_to(0, arg);
if (! safe_parse_maxint(&arg, &val))
return 1;
if (val>PXE_MAX_BLKSIZE)
val=PXE_MAX_BLKSIZE;
if (val<PXE_MIN_BLKSIZE)
val=PXE_MIN_BLKSIZE;
pxe_blksize=val;
}
else if (grub_memcmp(arg, "basedir", sizeof("basedir")-1)==0)
{
int n;
arg=skip_to(0, arg);
if (*arg==0)
{
grub_printf("No pathname\n");
errnum = ERR_BAD_ARGUMENT;
return 1;
}
if (*arg!='/')
{
grub_printf("Base directory must start with /\n");
errnum = ERR_BAD_ARGUMENT;
return 1;
}
n=grub_strlen(arg);
if (n>sizeof(pxe_tftp_open.FileName)-8)
{
grub_printf("Path too long\n");
errnum = ERR_BAD_ARGUMENT;
return 1;
}
grub_strcpy((char*)pxe_tftp_open.FileName,arg);
n--;
while ((n>=0) && (pxe_tftp_open.FileName[n]=='/'))
n--;
pxe_tftp_name=(char*)&pxe_tftp_open.FileName[n+1];
}
else if (grub_memcmp(arg, "keep", sizeof("keep")-1)==0)
pxe_keep=1;
else if (grub_memcmp(arg, "unload", sizeof("unload")-1)==0)
{
pxe_keep=0;
pxe_unload();
}
#ifdef PXE_DEBUG
else if (grub_memcmp(arg, "dump", sizeof("dump")-1)==0)
{
PXENV_GET_CACHED_INFO_t get_cached_info;
BOOTPLAYER *bp;
int val;
arg=skip_to(0, arg);
if (! safe_parse_maxint(&arg, &val))
return 1;
if ((val<1) || (val>3))
{
grub_printf("Invalid type\n");
errnum = ERR_BAD_ARGUMENT;
return 1;
}
get_cached_info.PacketType=val;
get_cached_info.Buffer=get_cached_info.BufferSize=0;
pxe_call(PXENV_GET_CACHED_INFO,&get_cached_info);
if (get_cached_info.Status)
return;
bp=LINEAR(get_cached_info.Buffer);
grub_printf("%X\n",(unsigned long)bp);
dump_block(0,bp,get_cached_info.BufferSize);
}
#endif
else
{
errnum = ERR_BAD_ARGUMENT;
return 1;
}
return 0;
}
#endif
#endif