1/*
2 * Copyright (c) 2000 Alfred Perlstein <alfred@freebsd.org>
3 * Copyright (c) 2000 Paul Saab <ps@freebsd.org>
4 * Copyright (c) 2000 John Baldwin <jhb@freebsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30
31#include <stand.h>
32#include <stddef.h>
33#include <string.h>
34#include <stdarg.h>
35#include <sys/param.h>
36
37#include <net/ethernet.h>
38#include <netinet/in_systm.h>
39#include <netinet/in.h>
40#include <netinet/ip.h>
41#include <netinet/udp.h>
42
43#include <net.h>
44#include <netif.h>
45#include <nfsv2.h>
46#include <iodesc.h>
47
48#include <bootp.h>
49#include <bootstrap.h>
50#include "libi386.h"
51#include "btxv86.h"
52#include "pxe.h"
53
54static pxenv_t	*pxenv_p = NULL;	/* PXENV+ */
55static pxe_t	*pxe_p	 = NULL;	/* !PXE */
56
57#ifdef PXE_DEBUG
58static int	pxe_debug = 0;
59#endif
60
61void		pxe_enable(void *pxeinfo);
62static void	(*pxe_call)(int func, void *ptr);
63static void	pxenv_call(int func, void *ptr);
64static void	bangpxe_call(int func, void *ptr);
65
66static int	pxe_init(void);
67static int	pxe_print(int verbose);
68static void	pxe_cleanup(void);
69
70static void	pxe_perror(int error);
71static int	pxe_netif_match(struct netif *nif, void *machdep_hint);
72static int	pxe_netif_probe(struct netif *nif, void *machdep_hint);
73static void	pxe_netif_init(struct iodesc *desc, void *machdep_hint);
74static ssize_t	pxe_netif_get(struct iodesc *, void **, time_t);
75static ssize_t	pxe_netif_put(struct iodesc *desc, void *pkt, size_t len);
76static void	pxe_netif_end(struct netif *nif);
77
78extern struct netif_stats	pxe_st[];
79extern uint16_t			__bangpxeseg;
80extern uint16_t			__bangpxeoff;
81extern void			__bangpxeentry(void);
82extern uint16_t			__pxenvseg;
83extern uint16_t			__pxenvoff;
84extern void			__pxenventry(void);
85
86struct netif_dif pxe_ifs[] = {
87	{
88		.dif_unit = 0,
89		.dif_nsel = 1,
90		.dif_stats = &pxe_st[0],
91		.dif_private = NULL,
92		.dif_used = 0
93	}
94};
95
96struct netif_stats pxe_st[nitems(pxe_ifs)];
97
98struct netif_driver pxenetif = {
99	.netif_bname = "pxenet",
100	.netif_match = pxe_netif_match,
101	.netif_probe = pxe_netif_probe,
102	.netif_init = pxe_netif_init,
103	.netif_get = pxe_netif_get,
104	.netif_put = pxe_netif_put,
105	.netif_end = pxe_netif_end,
106	.netif_ifs = pxe_ifs,
107	.netif_nifs = nitems(pxe_ifs)
108};
109
110struct netif_driver *netif_drivers[] = {
111	&pxenetif,
112	NULL
113};
114
115struct devsw pxedisk = {
116	.dv_name = "net",
117	.dv_type = DEVT_NET,
118	.dv_init = pxe_init,
119	.dv_strategy = NULL,	/* Will be set in pxe_init */
120	.dv_open = NULL,	/* Will be set in pxe_init */
121	.dv_close = NULL,	/* Will be set in pxe_init */
122	.dv_ioctl = noioctl,
123	.dv_print = pxe_print,
124	.dv_cleanup = pxe_cleanup
125};
126
127/*
128 * This function is called by the loader to enable PXE support if we
129 * are booted by PXE.  The passed in pointer is a pointer to the
130 * PXENV+ structure.
131 */
132void
133pxe_enable(void *pxeinfo)
134{
135	pxenv_p  = (pxenv_t *)pxeinfo;
136	pxe_p    = (pxe_t *)PTOV(pxenv_p->PXEPtr.segment * 16 +
137	    pxenv_p->PXEPtr.offset);
138	pxe_call = NULL;
139}
140
141/*
142 * return true if pxe structures are found/initialized,
143 * also figures out our IP information via the pxe cached info struct
144 */
145static int
146pxe_init(void)
147{
148	t_PXENV_GET_CACHED_INFO	*gci_p;
149	int	counter;
150	uint8_t checksum;
151	uint8_t *checkptr;
152	extern struct devsw netdev;
153
154	if (pxenv_p == NULL)
155		return (0);
156
157	/*  look for "PXENV+" */
158	if (bcmp((void *)pxenv_p->Signature, S_SIZE("PXENV+"))) {
159		pxenv_p = NULL;
160		return (0);
161	}
162
163	/* make sure the size is something we can handle */
164	if (pxenv_p->Length > sizeof (*pxenv_p)) {
165		printf("PXENV+ structure too large, ignoring\n");
166		pxenv_p = NULL;
167		return (0);
168	}
169
170	/*
171	 * do byte checksum:
172	 * add up each byte in the structure, the total should be 0
173	 */
174	checksum = 0;
175	checkptr = (uint8_t *)pxenv_p;
176	for (counter = 0; counter < pxenv_p->Length; counter++)
177		checksum += *checkptr++;
178	if (checksum != 0) {
179		printf("PXENV+ structure failed checksum, ignoring\n");
180		pxenv_p = NULL;
181		return (0);
182	}
183
184	/*
185	 * PXENV+ passed, so use that if !PXE is not available or
186	 * the checksum fails.
187	 */
188	pxe_call = pxenv_call;
189	if (pxenv_p->Version >= 0x0200) {
190		for (;;) {
191			if (bcmp((void *)pxe_p->Signature, S_SIZE("!PXE"))) {
192				pxe_p = NULL;
193				break;
194			}
195			checksum = 0;
196			checkptr = (uint8_t *)pxe_p;
197			for (counter = 0; counter < pxe_p->StructLength;
198			    counter++) {
199				checksum += *checkptr++;
200			}
201			if (checksum != 0) {
202				pxe_p = NULL;
203				break;
204			}
205			pxe_call = bangpxe_call;
206			break;
207		}
208	}
209
210	pxedisk.dv_open = netdev.dv_open;
211	pxedisk.dv_close = netdev.dv_close;
212	pxedisk.dv_strategy = netdev.dv_strategy;
213
214	printf("\nPXE version %d.%d, real mode entry point ",
215	    (uint8_t)(pxenv_p->Version >> 8),
216	    (uint8_t)(pxenv_p->Version & 0xFF));
217	if (pxe_call == bangpxe_call) {
218		printf("@%04x:%04x\n",
219		    pxe_p->EntryPointSP.segment,
220		    pxe_p->EntryPointSP.offset);
221	} else {
222		printf("@%04x:%04x\n",
223		    pxenv_p->RMEntry.segment, pxenv_p->RMEntry.offset);
224	}
225
226	gci_p = bio_alloc(sizeof (*gci_p));
227	if (gci_p == NULL) {
228		pxe_p = NULL;
229		return (0);
230	}
231	bzero(gci_p, sizeof (*gci_p));
232	gci_p->PacketType = PXENV_PACKET_TYPE_BINL_REPLY;
233	pxe_call(PXENV_GET_CACHED_INFO, gci_p);
234	if (gci_p->Status != 0) {
235		pxe_perror(gci_p->Status);
236		bio_free(gci_p, sizeof (*gci_p));
237		pxe_p = NULL;
238		return (0);
239	}
240
241	free(bootp_response);
242	if ((bootp_response = malloc(gci_p->BufferSize)) != NULL) {
243		bootp_response_size = gci_p->BufferSize;
244		bcopy(PTOV((gci_p->Buffer.segment << 4) + gci_p->Buffer.offset),
245		    bootp_response, bootp_response_size);
246	}
247
248	bio_free(gci_p, sizeof (*gci_p));
249	return (1);
250}
251
252static int
253pxe_print(int verbose)
254{
255	if (pxe_call == NULL)
256		return (0);
257
258	printf("%s devices:", pxedisk.dv_name);
259	if (pager_output("\n") != 0)
260		return (1);
261	printf("    %s0:", pxedisk.dv_name);
262	if (verbose) {
263		printf("    %s:%s", inet_ntoa(rootip), rootpath);
264	}
265	return (pager_output("\n"));
266}
267
268static void
269pxe_cleanup(void)
270{
271	t_PXENV_UNLOAD_STACK *unload_stack_p;
272	t_PXENV_UNDI_SHUTDOWN *undi_shutdown_p;
273
274	if (pxe_call == NULL)
275		return;
276
277	undi_shutdown_p = bio_alloc(sizeof (*undi_shutdown_p));
278	if (undi_shutdown_p != NULL) {
279		bzero(undi_shutdown_p, sizeof (*undi_shutdown_p));
280		pxe_call(PXENV_UNDI_SHUTDOWN, undi_shutdown_p);
281
282#ifdef PXE_DEBUG
283		if (pxe_debug && undi_shutdown_p->Status != 0)
284			printf("pxe_cleanup: UNDI_SHUTDOWN failed %x\n",
285			    undi_shutdown_p->Status);
286#endif
287		bio_free(undi_shutdown_p, sizeof (*undi_shutdown_p));
288	}
289
290	unload_stack_p = bio_alloc(sizeof (*unload_stack_p));
291	if (unload_stack_p != NULL) {
292		bzero(unload_stack_p, sizeof (*unload_stack_p));
293		pxe_call(PXENV_UNLOAD_STACK, unload_stack_p);
294
295#ifdef PXE_DEBUG
296		if (pxe_debug && unload_stack_p->Status != 0)
297			printf("pxe_cleanup: UNLOAD_STACK failed %x\n",
298			    unload_stack_p->Status);
299#endif
300		bio_free(unload_stack_p, sizeof (*unload_stack_p));
301	}
302}
303
304void
305pxe_perror(int err __unused)
306{
307}
308
309void
310pxenv_call(int func, void *ptr)
311{
312#ifdef PXE_DEBUG
313	if (pxe_debug)
314		printf("pxenv_call %x\n", func);
315#endif
316
317	bzero(&v86, sizeof (v86));
318
319	__pxenvseg = pxenv_p->RMEntry.segment;
320	__pxenvoff = pxenv_p->RMEntry.offset;
321
322	v86.ctl  = V86_ADDR | V86_CALLF | V86_FLAGS;
323	v86.es   = VTOPSEG(ptr);
324	v86.edi  = VTOPOFF(ptr);
325	v86.addr = (VTOPSEG(__pxenventry) << 16) | VTOPOFF(__pxenventry);
326	v86.ebx  = func;
327	v86int();
328	v86.ctl  = V86_FLAGS;
329}
330
331void
332bangpxe_call(int func, void *ptr)
333{
334#ifdef PXE_DEBUG
335	if (pxe_debug)
336		printf("bangpxe_call %x\n", func);
337#endif
338
339	bzero(&v86, sizeof (v86));
340
341	__bangpxeseg = pxe_p->EntryPointSP.segment;
342	__bangpxeoff = pxe_p->EntryPointSP.offset;
343
344	v86.ctl  = V86_ADDR | V86_CALLF | V86_FLAGS;
345	v86.edx  = VTOPSEG(ptr);
346	v86.eax  = VTOPOFF(ptr);
347	v86.addr = (VTOPSEG(__bangpxeentry) << 16) | VTOPOFF(__bangpxeentry);
348	v86.ebx  = func;
349	v86int();
350	v86.ctl  = V86_FLAGS;
351}
352
353
354static int
355pxe_netif_match(struct netif *nif __unused, void *machdep_hint __unused)
356{
357	return (1);
358}
359
360
361static int
362pxe_netif_probe(struct netif *nif __unused, void *machdep_hint __unused)
363{
364	if (pxe_call == NULL)
365		return (-1);
366
367	return (0);
368}
369
370static void
371pxe_netif_end(struct netif *nif __unused)
372{
373	t_PXENV_UNDI_CLOSE *undi_close_p;
374
375	undi_close_p = bio_alloc(sizeof (*undi_close_p));
376	if (undi_close_p != NULL) {
377		bzero(undi_close_p, sizeof (*undi_close_p));
378		pxe_call(PXENV_UNDI_CLOSE, undi_close_p);
379		if (undi_close_p->Status != 0)
380			printf("undi close failed: %x\n", undi_close_p->Status);
381		bio_free(undi_close_p, sizeof (*undi_close_p));
382	}
383}
384
385static void
386pxe_netif_init(struct iodesc *desc, void *machdep_hint __unused)
387{
388	t_PXENV_UNDI_GET_INFORMATION *undi_info_p;
389	t_PXENV_UNDI_OPEN *undi_open_p;
390	uint8_t *mac;
391	int i, len;
392
393	undi_info_p = bio_alloc(sizeof (*undi_info_p));
394	if (undi_info_p == NULL)
395		return;
396
397	bzero(undi_info_p, sizeof (*undi_info_p));
398	pxe_call(PXENV_UNDI_GET_INFORMATION, undi_info_p);
399	if (undi_info_p->Status != 0) {
400		printf("undi get info failed: %x\n", undi_info_p->Status);
401		bio_free(undi_info_p, sizeof (*undi_info_p));
402		return;
403	}
404
405	/* Make sure the CurrentNodeAddress is valid. */
406	for (i = 0; i < undi_info_p->HwAddrLen; ++i) {
407		if (undi_info_p->CurrentNodeAddress[i] != 0)
408			break;
409	}
410	if (i < undi_info_p->HwAddrLen) {
411		for (i = 0; i < undi_info_p->HwAddrLen; ++i) {
412			if (undi_info_p->CurrentNodeAddress[i] != 0xff)
413				break;
414		}
415	}
416	if (i < undi_info_p->HwAddrLen)
417		mac = undi_info_p->CurrentNodeAddress;
418	else
419		mac = undi_info_p->PermNodeAddress;
420
421	len = min(sizeof (desc->myea), undi_info_p->HwAddrLen);
422	for (i = 0; i < len; ++i) {
423		desc->myea[i] = mac[i];
424	}
425
426	if (bootp_response != NULL)
427		desc->xid = bootp_response->bp_xid;
428	else
429		desc->xid = 0;
430
431	bio_free(undi_info_p, sizeof (*undi_info_p));
432	undi_open_p = bio_alloc(sizeof (*undi_open_p));
433	if (undi_open_p == NULL)
434		return;
435	bzero(undi_open_p, sizeof (*undi_open_p));
436	undi_open_p->PktFilter = FLTR_DIRECTED | FLTR_BRDCST;
437	pxe_call(PXENV_UNDI_OPEN, undi_open_p);
438	if (undi_open_p->Status != 0)
439		printf("undi open failed: %x\n", undi_open_p->Status);
440	bio_free(undi_open_p, sizeof (*undi_open_p));
441}
442
443static int
444pxe_netif_receive(void **pkt)
445{
446	t_PXENV_UNDI_ISR *isr;
447	char *buf, *ptr, *frame;
448	size_t size, rsize;
449
450	isr = bio_alloc(sizeof (*isr));
451	if (isr == NULL)
452		return (-1);
453
454	bzero(isr, sizeof (*isr));
455	isr->FuncFlag = PXENV_UNDI_ISR_IN_START;
456	pxe_call(PXENV_UNDI_ISR, isr);
457	if (isr->Status != 0) {
458		bio_free(isr, sizeof (*isr));
459		return (-1);
460	}
461
462	bzero(isr, sizeof (*isr));
463	isr->FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
464	pxe_call(PXENV_UNDI_ISR, isr);
465	if (isr->Status != 0) {
466		bio_free(isr, sizeof (*isr));
467		return (-1);
468	}
469
470	while (isr->FuncFlag != PXENV_UNDI_ISR_OUT_RECEIVE) {
471		if (isr->Status != 0 ||
472		    isr->FuncFlag == PXENV_UNDI_ISR_OUT_DONE) {
473			bio_free(isr, sizeof (*isr));
474			return (-1);
475		}
476		bzero(isr, sizeof (*isr));
477		isr->FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
478		pxe_call(PXENV_UNDI_ISR, isr);
479	}
480
481	size = isr->FrameLength;
482	buf = malloc(size + ETHER_ALIGN);
483	if (buf == NULL) {
484		bio_free(isr, sizeof (*isr));
485		return (-1);
486	}
487	ptr = buf + ETHER_ALIGN;
488	rsize = 0;
489
490	while (rsize < size) {
491		frame = (char *)((uintptr_t)isr->Frame.segment << 4);
492		frame += isr->Frame.offset;
493		bcopy(PTOV(frame), ptr, isr->BufferLength);
494		ptr += isr->BufferLength;
495		rsize += isr->BufferLength;
496
497		bzero(isr, sizeof (*isr));
498		isr->FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
499		pxe_call(PXENV_UNDI_ISR, isr);
500		if (isr->Status != 0) {
501			bio_free(isr, sizeof (*isr));
502			free(buf);
503			return (-1);
504		}
505
506		/* Did we got another update? */
507		if (isr->FuncFlag == PXENV_UNDI_ISR_OUT_RECEIVE)
508			continue;
509		break;
510	}
511
512	*pkt = buf;
513	bio_free(isr, sizeof (*isr));
514	return (rsize);
515}
516
517static ssize_t
518pxe_netif_get(struct iodesc *desc __unused, void **pkt, time_t timeout)
519{
520	time_t t;
521	void *ptr;
522	ssize_t ret = -1;
523
524	t = getsecs();
525	while ((getsecs() - t) < timeout) {
526		ret = pxe_netif_receive(&ptr);
527		if (ret != -1) {
528			*pkt = ptr;
529			break;
530		}
531	}
532	return (ret);
533}
534
535static ssize_t
536pxe_netif_put(struct iodesc *desc __unused, void *pkt, size_t len)
537{
538	t_PXENV_UNDI_TRANSMIT *trans_p;
539	t_PXENV_UNDI_TBD *tbd_p;
540	char *data;
541	ssize_t rv = -1;
542
543	trans_p = bio_alloc(sizeof (*trans_p));
544	tbd_p = bio_alloc(sizeof (*tbd_p));
545	data = bio_alloc(len);
546
547	if (trans_p != NULL && tbd_p != NULL && data != NULL) {
548		bzero(trans_p, sizeof (*trans_p));
549		bzero(tbd_p, sizeof (*tbd_p));
550
551		trans_p->TBD.segment = VTOPSEG(tbd_p);
552		trans_p->TBD.offset  = VTOPOFF(tbd_p);
553
554		tbd_p->ImmedLength = len;
555		tbd_p->Xmit.segment = VTOPSEG(data);
556		tbd_p->Xmit.offset  = VTOPOFF(data);
557		bcopy(pkt, data, len);
558
559		pxe_call(PXENV_UNDI_TRANSMIT, trans_p);
560		if (trans_p->Status == 0)
561			rv = len;
562	}
563
564	bio_free(data, len);
565	bio_free(tbd_p, sizeof (*tbd_p));
566	bio_free(trans_p, sizeof (*trans_p));
567	return (rv);
568}
569