Centralise construction of the DHCP request and response packets.
[people/indolent/gpxe.git/.git] / src / interface / pxe / pxe_preboot.c
1 /** @file
2  *
3  * PXE Preboot API
4  *
5  */
6
7 /* PXE API interface for Etherboot.
8  *
9  * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or any later version.
15  *
16  * This program is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24  */
25
26 #include <stdint.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <gpxe/uaccess.h>
30 #include <gpxe/dhcp.h>
31 #include <gpxe/device.h>
32 #include <gpxe/netdevice.h>
33 #include <gpxe/isapnp.h>
34 #include <gpxe/init.h>
35 #include <basemem_packet.h>
36 #include "pxe.h"
37 #include "pxe_call.h"
38
39 /** Filename used for last TFTP request
40  *
41  * This is a bug-for-bug compatibility hack needed in order to work
42  * with Microsoft Remote Installation Services (RIS).  The filename
43  * used in a call to PXENV_RESTART_TFTP must be returned as the DHCP
44  * filename in subsequent calls to PXENV_GET_CACHED_INFO.
45  */
46 static char *pxe_ris_filename = NULL;
47
48 /* Avoid dragging in isapnp.o unnecessarily */
49 uint16_t isapnp_read_port;
50
51 /**
52  * UNLOAD BASE CODE STACK
53  *
54  * @v None                              -
55  * @ret ...
56  *
57  */
58 PXENV_EXIT_t pxenv_unload_stack ( struct s_PXENV_UNLOAD_STACK *unload_stack ) {
59         DBG ( "PXENV_UNLOAD_STACK" );
60
61         unload_stack->Status = PXENV_STATUS_SUCCESS;
62         return PXENV_EXIT_SUCCESS;
63 }
64
65 /* PXENV_GET_CACHED_INFO
66  *
67  * Status: working
68  */
69 PXENV_EXIT_t pxenv_get_cached_info ( struct s_PXENV_GET_CACHED_INFO
70                                      *get_cached_info ) {
71         struct dhcp_packet dhcppkt;
72         int ( * dhcp_packet_creator ) ( struct net_device *, int,
73                                         struct dhcp_option_block *, void *,
74                                         size_t, struct dhcp_packet * );
75         unsigned int msgtype;
76         void *data = NULL;
77         size_t len;
78         userptr_t buffer;
79         int rc;
80
81         DBG ( "PXENV_GET_CACHED_INFO %d", get_cached_info->PacketType );
82
83         DBG ( " to %04x:%04x+%x", get_cached_info->Buffer.segment,
84               get_cached_info->Buffer.offset, get_cached_info->BufferSize );
85
86         /* This is really, really awkward to support with our multiple
87          * sources of options.
88          */
89         len = get_cached_info->BufferSize;
90         if ( len == 0 ) {
91                 len = sizeof ( basemem_packet );
92                 get_cached_info->Buffer.segment = rm_ds;
93                 get_cached_info->Buffer.offset =
94                         ( unsigned int ) ( & __from_data16 ( basemem_packet ) );
95                 get_cached_info->BufferLimit = len;
96         }
97
98         /* Allocate space for temporary copy */
99         data = malloc ( len );
100         if ( ! data ) {
101                 DBG ( " out of memory" );
102                 goto err;
103         }
104
105         /* Construct DHCP packet */
106         if ( get_cached_info->PacketType == PXENV_PACKET_TYPE_DHCP_DISCOVER ) {
107                 dhcp_packet_creator = create_dhcp_request;
108                 msgtype = DHCPDISCOVER;
109         } else {
110                 dhcp_packet_creator = create_dhcp_response;
111                 msgtype = DHCPACK;
112         }
113         if ( ( rc = dhcp_packet_creator ( pxe_netdev, msgtype, NULL,
114                                           data, len, &dhcppkt ) ) != 0 ) {
115                 DBG ( " failed to build packet" );
116                 goto err;
117         }
118
119         /* Overwrite filename to work around Microsoft RIS bug */
120         if ( pxe_ris_filename ) {
121                 strncpy ( dhcppkt.dhcphdr->file, pxe_ris_filename,
122                           sizeof ( dhcppkt.dhcphdr->file ) );
123         }
124
125         /* Copy packet to client buffer */
126         buffer = real_to_user ( get_cached_info->Buffer.segment,
127                                 get_cached_info->Buffer.offset );
128         len = dhcppkt.len;
129         copy_to_user ( buffer, 0, data, len );
130         get_cached_info->BufferSize = len;
131
132         free ( data );
133         get_cached_info->Status = PXENV_STATUS_SUCCESS;
134         return PXENV_EXIT_SUCCESS;
135
136  err:
137         if ( data )
138                 free ( data );
139         get_cached_info->Status = PXENV_STATUS_OUT_OF_RESOURCES;
140         return PXENV_EXIT_FAILURE;
141 }
142
143 /* PXENV_RESTART_TFTP
144  *
145  * Status: working
146  */
147 PXENV_EXIT_t pxenv_restart_tftp ( struct s_PXENV_TFTP_READ_FILE
148                                   *restart_tftp ) {
149         PXENV_EXIT_t tftp_exit;
150
151         DBG ( "PXENV_RESTART_TFTP " );
152
153         /* Work around Microsoft RIS bug */
154         free ( pxe_ris_filename );
155         pxe_ris_filename = strdup ( ( char * ) restart_tftp->FileName );
156         if ( ! pxe_ris_filename ) {
157                 restart_tftp->Status = PXENV_STATUS_OUT_OF_RESOURCES;
158                 return PXENV_EXIT_FAILURE;
159         }
160
161         /* Words cannot describe the complete mismatch between the PXE
162          * specification and any possible version of reality...
163          */
164         restart_tftp->Buffer = PXE_LOAD_PHYS; /* Fixed by spec, apparently */
165         restart_tftp->BufferSize = ( 0xa0000 - PXE_LOAD_PHYS ); /* Near enough */
166         tftp_exit = pxenv_tftp_read_file ( restart_tftp );
167         if ( tftp_exit != PXENV_EXIT_SUCCESS )
168                 return tftp_exit;
169
170         /* Fire up the new NBP */
171         restart_tftp->Status = pxe_start_nbp();
172
173         /* Not sure what "SUCCESS" actually means, since we can only
174          * return if the new NBP failed to boot...
175          */
176         return PXENV_EXIT_SUCCESS;
177 }
178
179 /* PXENV_START_UNDI
180  *
181  * Status: working
182  */
183 PXENV_EXIT_t pxenv_start_undi ( struct s_PXENV_START_UNDI *start_undi ) {
184         unsigned int bus_type;
185         unsigned int location;
186         struct net_device *netdev;
187
188         DBG ( "PXENV_START_UNDI %04x:%04x:%04x",
189               start_undi->AX, start_undi->BX, start_undi->DX );
190
191         /* Determine bus type and location.  Use a heuristic to decide
192          * whether we are PCI or ISAPnP
193          */
194         if ( ( start_undi->DX >= ISAPNP_READ_PORT_MIN ) &&
195              ( start_undi->DX <= ISAPNP_READ_PORT_MAX ) &&
196              ( start_undi->BX >= ISAPNP_CSN_MIN ) &&
197              ( start_undi->BX <= ISAPNP_CSN_MAX ) ) {
198                 bus_type = BUS_TYPE_ISAPNP;
199                 location = start_undi->BX;
200                 /* Record ISAPnP read port for use by isapnp.c */
201                 isapnp_read_port = start_undi->DX;
202         } else {
203                 bus_type = BUS_TYPE_PCI;
204                 location = start_undi->AX;
205         }
206
207         /* Probe for devices, etc. */
208         startup();
209
210         /* Look for a matching net device */
211         netdev = find_netdev_by_location ( bus_type, location );
212         if ( ! netdev ) {
213                 DBG ( " no net device found" );
214                 start_undi->Status = PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC;
215                 return PXENV_EXIT_FAILURE;
216         }
217         DBG ( " using netdev %s", netdev->name );
218
219         /* Save as PXE net device */
220         pxe_set_netdev ( netdev );
221
222         /* Hook INT 1A */
223         pxe_hook_int1a();
224
225         start_undi->Status = PXENV_STATUS_SUCCESS;
226         return PXENV_EXIT_SUCCESS;
227 }
228
229 /* PXENV_STOP_UNDI
230  *
231  * Status: working
232  */
233 PXENV_EXIT_t pxenv_stop_undi ( struct s_PXENV_STOP_UNDI *stop_undi ) {
234         DBG ( "PXENV_STOP_UNDI" );
235
236         /* Unhook INT 1A */
237         pxe_unhook_int1a();
238
239         /* Clear PXE net device */
240         pxe_set_netdev ( NULL );
241
242         /* Prepare for unload */
243         shutdown();
244
245         stop_undi->Status = PXENV_STATUS_SUCCESS;
246         return PXENV_EXIT_SUCCESS;
247 }
248
249 /* PXENV_START_BASE
250  *
251  * Status: won't implement (requires major structural changes)
252  */
253 PXENV_EXIT_t pxenv_start_base ( struct s_PXENV_START_BASE *start_base ) {
254         DBG ( "PXENV_START_BASE" );
255
256         start_base->Status = PXENV_STATUS_UNSUPPORTED;
257         return PXENV_EXIT_FAILURE;
258 }
259
260 /* PXENV_STOP_BASE
261  *
262  * Status: working
263  */
264 PXENV_EXIT_t pxenv_stop_base ( struct s_PXENV_STOP_BASE *stop_base ) {
265         DBG ( "PXENV_STOP_BASE" );
266
267         /* The only time we will be called is when the NBP is trying
268          * to shut down the PXE stack.  There's nothing we need to do
269          * in this call.
270          */
271
272         stop_base->Status = PXENV_STATUS_SUCCESS;
273         return PXENV_EXIT_SUCCESS;
274 }