UNDI loader routine now works
[people/xl0/gpxe.git] / src / arch / i386 / drivers / bus / pxedrv.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <pxe.h>
23 #include <realmode.h>
24 #include <bios.h>
25 #include <pnpbios.h>
26
27 /** @file
28  *
29  * PXE drivers
30  *
31  */
32
33 static LIST_HEAD ( pxe_drivers );
34
35 /**
36  * Parse PXE ROM ID structure
37  *
38  * @v pxedrv            PXE driver
39  * @v pxeromid          Offset within ROM to PXE ROM ID structure
40  * @ret rc              Return status code
41  */
42 static int pxedrv_parse_pxeromid ( struct pxe_driver *pxedrv,
43                                    unsigned int pxeromid ) {
44         struct undi_rom_id undi_rom_id;
45         unsigned int undiloader;
46
47         DBGC ( pxedrv, "PXEDRV %p has PXE ROM ID at %04x:%04x\n", pxedrv,
48                pxedrv->rom_segment, pxeromid );
49
50         /* Read PXE ROM ID structure and verify */
51         copy_from_real ( &undi_rom_id, pxedrv->rom_segment, pxeromid,
52                          sizeof ( undi_rom_id ) );
53         if ( undi_rom_id.Signature != UNDI_ROM_ID_SIGNATURE ) {
54                 DBGC ( pxedrv, "PXEDRV %p has bad PXE ROM ID signature "
55                        "%08lx\n", pxedrv, undi_rom_id.Signature );
56                 return -EINVAL;
57         }
58
59         /* Check for UNDI loader */
60         undiloader = undi_rom_id.UNDILoader;
61         if ( ! undiloader ) {
62                 DBGC ( pxedrv, "PXEDRV %p has no UNDI loader\n", pxedrv );
63                 return -EINVAL;
64         }
65
66         /* Fill in PXE driver loader fields */
67         pxedrv->loader_entry.segment = pxedrv->rom_segment;
68         pxedrv->loader_entry.offset = undiloader;
69         pxedrv->code_size = undi_rom_id.CodeSize;
70         pxedrv->data_size = undi_rom_id.DataSize;
71
72         DBGC ( pxedrv, "PXEDRV %p has UNDI loader at %04x:%04x "
73                "(code %04x data %04x)\n", pxedrv,
74                pxedrv->loader_entry.segment, pxedrv->loader_entry.offset,
75                pxedrv->code_size, pxedrv->data_size );
76         return 0;
77 }
78
79 /**
80  * Parse PCI expansion header
81  *
82  * @v pxedrv            PXE driver
83  * @v pcirheader        Offset within ROM to PCI expansion header
84  */
85 static int pxedrv_parse_pcirheader ( struct pxe_driver *pxedrv,
86                                      unsigned int pcirheader ) {
87         struct pcir_header pcir_header;
88
89         DBGC ( pxedrv, "PXEDRV %p has PCI expansion header at %04x:%04x\n",
90                pxedrv, pxedrv->rom_segment, pcirheader );
91
92         /* Read PCI expansion header and verify */
93         copy_from_real ( &pcir_header, pxedrv->rom_segment, pcirheader,
94                          sizeof ( pcir_header ) );
95         if ( pcir_header.signature != PCIR_SIGNATURE ) {
96                 DBGC ( pxedrv, "PXEDRV %p has bad PCI expansion header "
97                        "signature %08lx\n", pxedrv, pcir_header.signature );
98                 return -EINVAL;
99         }
100         DBGC ( pxedrv, "PXEDRV %p is a PCI ROM\n", pxedrv );
101
102         /* Fill in PXE driver PCI device fields */
103         pxedrv->bus_type = PCI_NIC;
104         pxedrv->bus_id.pci.vendor_id = pcir_header.vendor_id;
105         pxedrv->bus_id.pci.device_id = pcir_header.device_id;
106
107         DBGC ( pxedrv, "PXEDRV %p is for PCI devices %04x:%04x\n", pxedrv,
108                pxedrv->bus_id.pci.vendor_id, pxedrv->bus_id.pci.device_id );
109         return 0;
110         
111 }
112
113 /**
114  * Create PXE driver for expansion ROM
115  *
116  * @v rom_segment       ROM segment address
117  * @ret rc              Return status code
118  */
119 static int pxedrv_probe_rom ( unsigned int rom_segment ) {
120         struct pxe_driver *pxedrv = NULL;
121         struct undi_rom rom;
122         size_t rom_len;
123         unsigned int pxeromid;
124         unsigned int pcirheader;
125         int rc;
126
127         /* Read expansion ROM header and verify */
128         copy_from_real ( &rom, rom_segment, 0, sizeof ( rom ) );
129         if ( rom.Signature != ROM_SIGNATURE ) {
130                 rc = -EINVAL;
131                 goto err;
132         }
133         rom_len = ( rom.ROMLength * 512 );
134
135         /* Allocate memory for PXE driver */
136         pxedrv = malloc ( sizeof ( *pxedrv ) );
137         if ( ! pxedrv ) {
138                 DBG ( "Could not allocate PXE driver structure\n" );
139                 rc = -ENOMEM;
140                 goto err;
141         }
142         memset ( pxedrv, 0, sizeof ( *pxedrv ) );
143         DBGC ( pxedrv, "PXEDRV %p trying expansion ROM at %04x:0000 (%zdkB)\n",
144                pxedrv, rom_segment, ( rom_len / 1024 ) );
145         pxedrv->rom_segment = rom_segment;
146
147         /* Check for and parse PXE ROM ID */
148         pxeromid = rom.PXEROMID;
149         if ( ! pxeromid ) {
150                 DBGC ( pxedrv, "PXEDRV %p has no PXE ROM ID\n", pxedrv );
151                 rc = -EINVAL;
152                 goto err;
153         }
154         if ( pxeromid > rom_len ) {
155                 DBGC ( pxedrv, "PXEDRV %p PXE ROM ID outside ROM\n", pxedrv );
156                 rc = -EINVAL;
157                 goto err;
158         }
159         if ( ( rc = pxedrv_parse_pxeromid ( pxedrv, pxeromid ) ) != 0 )
160                 goto err;
161
162         /* Parse PCIR header, if present */
163         pcirheader = rom.PCIRHeader;
164         if ( pcirheader )
165                 pxedrv_parse_pcirheader ( pxedrv, pcirheader );
166
167         /* Add to PXE driver list and return */
168         DBGC ( pxedrv, "PXEDRV %p registered\n", pxedrv );
169         list_add ( &pxedrv->list, &pxe_drivers );
170         return 0;
171
172  err:
173         free ( pxedrv );
174         return rc;
175 }
176
177 /**
178  * Create PXE drivers for all possible expansion ROMs
179  *
180  * @ret 
181  */
182 static void pxedrv_probe_all_roms ( void ) {
183         static int probed = 0;
184         unsigned int rom_segment;
185
186         /* Perform probe only once */
187         if ( probed )
188                 return;
189
190         DBG ( "Scanning for PXE expansion ROMs\n" );
191
192         /* Scan through expansion ROM region at 2kB intervals */
193         for ( rom_segment = 0xc000 ; rom_segment < 0x10000 ;
194               rom_segment += 0x80 ) {
195                 pxedrv_probe_rom ( rom_segment );
196         }
197
198         probed = 1;
199 }
200
201 /**
202  * Find PXE driver for PCI device
203  *
204  * @v vendor_id         PCI vendor ID
205  * @v device_id         PCI device ID
206  * @v rombase           ROM base address, or 0 for any
207  * @ret pxedrv          PXE driver, or NULL
208  */
209 struct pxe_driver * pxedrv_find_pci_driver ( unsigned int vendor_id,
210                                              unsigned int device_id,
211                                              unsigned int rombase ) {
212         struct pxe_driver *pxedrv;
213
214         pxedrv_probe_all_roms();
215
216         list_for_each_entry ( pxedrv, &pxe_drivers, list ) {
217                 if ( pxedrv->bus_type != PCI_NIC )
218                         continue;
219                 if ( pxedrv->bus_id.pci.vendor_id != vendor_id )
220                         continue;
221                 if ( pxedrv->bus_id.pci.device_id != device_id )
222                         continue;
223                 if ( rombase && ( ( pxedrv->rom_segment << 4 ) != rombase ) )
224                         continue;
225                 DBGC ( pxedrv, "PXEDRV %p matched PCI %04x:%04x (%08x)\n",
226                        pxedrv, vendor_id, device_id, rombase );
227                 return pxedrv;
228         }
229
230         DBG ( "No PXE driver matched PCI %04x:%04x (%08x)\n",
231               vendor_id, device_id, rombase );
232         return NULL;
233 }
234
235 /** Parameter block for calling UNDI loader */
236 static struct s_UNDI_LOADER __data16 ( undi_loader );
237 #define undi_loader __use_data16 ( undi_loader )
238
239 /** UNDI loader entry point */
240 static SEGOFF16_t __data16 ( undi_loader_entry );
241 #define undi_loader_entry __use_data16 ( undi_loader_entry )
242
243 /**
244  * Call UNDI loader to create a pixie
245  *
246  * @v pxedrv            PXE driver
247  * @v pxe               PXE device to be created
248  * @v pci_busdevfn      PCI bus:dev.fn (PCI devices only), or 0
249  * @v isapnp_csn        ISAPnP Card Select Number, or -1U
250  * @v isapnp_read_port  ISAPnP read port, or -1U
251  * @ret rc              Return status code
252  */
253 static int pxedrv_load ( struct pxe_driver *pxedrv, struct pxe_device *pxe,
254                          unsigned int pci_busdevfn, unsigned int isapnp_csn,
255                          unsigned int isapnp_read_port ) {
256         int pnpbios_offset;
257         uint16_t fbms;
258         unsigned int fbms_seg;
259         int discard;
260         uint16_t exit;
261         int rc;
262
263         /* Record device location information */
264         memset ( &undi_loader, 0, sizeof ( undi_loader ) );
265         undi_loader.AX = pci_busdevfn;
266         undi_loader.BX = isapnp_csn;
267         undi_loader.DX = isapnp_read_port;
268
269         /* Set up PnP BIOS pointer, if PnP BIOS present */
270         pnpbios_offset = find_pnp_bios();
271         if ( pnpbios_offset >= 0 ) {
272                 undi_loader.ES = BIOS_SEG;
273                 undi_loader.DI = pnpbios_offset;
274         }
275
276         /* Allocate base memory for PXE stack */
277         get_real ( fbms, BDA_SEG, BDA_FBMS );
278         fbms_seg = ( fbms << 6 );
279         fbms_seg -= ( ( pxedrv->data_size + 0x0f ) >> 4 );
280         undi_loader.UNDI_DS = fbms_seg;
281         fbms_seg -= ( ( pxedrv->code_size + 0x0f ) >> 4 );
282         undi_loader.UNDI_CS = fbms_seg;
283         DBGC ( pxedrv, "PXEDRV %p loading to CS %04x and DS %04x\n", pxedrv,
284                undi_loader.UNDI_CS, undi_loader.UNDI_DS );
285
286         /* Call loader */
287         undi_loader_entry = pxedrv->loader_entry;
288         __asm__ __volatile__ ( REAL_CODE ( "pushw %%ds\n\t"
289                                            "pushw %w0\n\t"
290                                            "lcall *%c3\n\t"
291                                            "addw $4, %%sp\n\t" )
292                                : "=a" ( exit ), "=r" ( discard )
293                                : "0" ( & __from_data16 ( undi_loader ) ),
294                                  "p" ( & __from_data16 ( undi_loader_entry )));
295         if ( exit != PXENV_EXIT_SUCCESS ) {
296                 rc = -undi_loader.Status;
297                 if ( rc == 0 ) /* Paranoia */
298                         rc = -EIO;
299                 DBGC ( pxedrv, "PXEDRV %p loader failed: %s\n", pxedrv,
300                        strerror ( rc ) );
301                 return rc;
302         }
303
304         /* Update free base memory counter */
305         fbms = ( fbms_seg >> 6 );
306         put_real ( fbms, BDA_SEG, BDA_FBMS );
307
308         /* Record location of pixie in PXE device structure */
309         pxe->pxenv = undi_loader.PXENVptr;
310         pxe->ppxe = undi_loader.PXEptr;
311         return 0;
312 }
313
314 /**
315  * Call UNDI loader to create a pixie
316  *
317  * @v pxedrv            PXE driver
318  * @v pxe               PXE device to be created
319  * @v pci_busdevfn      PCI bus:dev.fn
320  * @ret rc              Return status code
321  */
322 int pxedrv_load_pci ( struct pxe_driver *pxedrv, struct pxe_device *pxe,
323                       unsigned int bus, unsigned int devfn ) {
324         return pxedrv_load ( pxedrv, pxe, ( ( bus << 8 ) | devfn ), -1U, -1U );
325 }