1/**************************************************************************
2Etherboot -  BOOTP/TFTP Bootstrap Program
3Bochs Pseudo NIC driver for Etherboot
4***************************************************************************/
5
6/*
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2, or (at
10 * your option) any later version.
11 *
12 * See pnic_api.h for an explanation of the Bochs Pseudo NIC.
13 */
14
15FILE_LICENCE ( GPL2_OR_LATER );
16
17#include <stdint.h>
18#include <stdio.h>
19#include <gpxe/io.h>
20#include <errno.h>
21#include <gpxe/pci.h>
22#include <gpxe/if_ether.h>
23#include <gpxe/ethernet.h>
24#include <gpxe/iobuf.h>
25#include <gpxe/netdevice.h>
26
27#include "pnic_api.h"
28
29struct pnic {
30	unsigned short ioaddr;
31};
32
33/*
34 * Utility functions: issue a PNIC command, retrieve result.  Use
35 * pnic_command_quiet if you don't want failure codes to be
36 * automatically printed.  Returns the PNIC status code.
37 *
38 * Set output_length to NULL only if you expect to receive exactly
39 * output_max_length bytes, otherwise it'll complain that you didn't
40 * get enough data (on the assumption that if you not interested in
41 * discovering the output length then you're expecting a fixed amount
42 * of data).
43 */
44
45static uint16_t pnic_command_quiet ( struct pnic *pnic, uint16_t command,
46				     const void *input, uint16_t input_length,
47				     void *output, uint16_t output_max_length,
48				     uint16_t *output_length ) {
49	uint16_t status;
50	uint16_t _output_length;
51
52	if ( input != NULL ) {
53		/* Write input length */
54		outw ( input_length, pnic->ioaddr + PNIC_REG_LEN );
55		/* Write input data */
56		outsb ( pnic->ioaddr + PNIC_REG_DATA, input, input_length );
57	}
58	/* Write command */
59	outw ( command, pnic->ioaddr + PNIC_REG_CMD );
60	/* Retrieve status */
61	status = inw ( pnic->ioaddr + PNIC_REG_STAT );
62	/* Retrieve output length */
63	_output_length = inw ( pnic->ioaddr + PNIC_REG_LEN );
64	if ( output_length == NULL ) {
65		if ( _output_length != output_max_length ) {
66			printf ( "pnic_command %#hx: wrong data length "
67				 "returned (expected %d, got %d)\n", command,
68				 output_max_length, _output_length );
69		}
70	} else {
71		*output_length = _output_length;
72	}
73	if ( output != NULL ) {
74		if ( _output_length > output_max_length ) {
75			printf ( "pnic_command %#hx: output buffer too small "
76				 "(have %d, need %d)\n", command,
77				 output_max_length, _output_length );
78			_output_length = output_max_length;
79		}
80		/* Retrieve output data */
81		insb ( pnic->ioaddr + PNIC_REG_DATA, output, _output_length );
82	}
83	return status;
84}
85
86static uint16_t pnic_command ( struct pnic *pnic, uint16_t command,
87			       const void *input, uint16_t input_length,
88			       void *output, uint16_t output_max_length,
89			       uint16_t *output_length ) {
90	uint16_t status = pnic_command_quiet ( pnic, command,
91					       input, input_length,
92					       output, output_max_length,
93					       output_length );
94	if ( status == PNIC_STATUS_OK ) return status;
95	printf ( "PNIC command %#hx (len %#hx) failed with status %#hx\n",
96		 command, input_length, status );
97	return status;
98}
99
100/* Check API version matches that of NIC */
101static int pnic_api_check ( uint16_t api_version ) {
102	if ( api_version != PNIC_API_VERSION ) {
103		printf ( "Warning: API version mismatch! "
104			 "(NIC's is %d.%d, ours is %d.%d)\n",
105			 api_version >> 8, api_version & 0xff,
106			 PNIC_API_VERSION >> 8, PNIC_API_VERSION & 0xff );
107	}
108	if ( api_version < PNIC_API_VERSION ) {
109		printf ( "** You may need to update your copy of Bochs **\n" );
110	}
111	return ( api_version == PNIC_API_VERSION );
112}
113
114/**************************************************************************
115POLL - Wait for a frame
116***************************************************************************/
117static void pnic_poll ( struct net_device *netdev ) {
118	struct pnic *pnic = netdev->priv;
119	struct io_buffer *iobuf;
120	uint16_t length;
121	uint16_t qlen;
122
123	/* Fetch all available packets */
124	while ( 1 ) {
125		if ( pnic_command ( pnic, PNIC_CMD_RECV_QLEN, NULL, 0,
126				    &qlen, sizeof ( qlen ), NULL )
127		     != PNIC_STATUS_OK )
128			return;
129		if ( qlen == 0 )
130			return;
131		iobuf = alloc_iob ( ETH_FRAME_LEN );
132		if ( ! iobuf ) {
133			DBG ( "could not allocate buffer\n" );
134			netdev_rx_err ( netdev, NULL, -ENOMEM );
135			return;
136		}
137		if ( pnic_command ( pnic, PNIC_CMD_RECV, NULL, 0,
138				    iobuf->data, ETH_FRAME_LEN, &length )
139		     != PNIC_STATUS_OK ) {
140			netdev_rx_err ( netdev, iobuf, -EIO );
141			return;
142		}
143		iob_put ( iobuf, length );
144		netdev_rx ( netdev, iobuf );
145	}
146}
147
148/**************************************************************************
149TRANSMIT - Transmit a frame
150***************************************************************************/
151static int pnic_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) {
152	struct pnic *pnic = netdev->priv;
153
154	/* Pad the packet */
155	iob_pad ( iobuf, ETH_ZLEN );
156
157	/* Send packet */
158	pnic_command ( pnic, PNIC_CMD_XMIT, iobuf->data, iob_len ( iobuf ),
159		       NULL, 0, NULL );
160
161	netdev_tx_complete ( netdev, iobuf );
162	return 0;
163}
164
165/**************************************************************************
166OPEN - Open network device
167***************************************************************************/
168static int pnic_open ( struct net_device *netdev __unused ) {
169	/* Nothing to do */
170	return 0;
171}
172
173/**************************************************************************
174CLOSE - Close network device
175***************************************************************************/
176static void pnic_close ( struct net_device *netdev __unused ) {
177	/* Nothing to do */
178}
179
180/**************************************************************************
181IRQ - Enable/disable interrupts
182***************************************************************************/
183static void pnic_irq ( struct net_device *netdev, int enable ) {
184	struct pnic *pnic = netdev->priv;
185	uint8_t mask = ( enable ? 1 : 0 );
186
187	pnic_command ( pnic, PNIC_CMD_MASK_IRQ, &mask, sizeof ( mask ),
188		       NULL, 0, NULL );
189}
190
191/**************************************************************************
192OPERATIONS TABLE
193***************************************************************************/
194static struct net_device_operations pnic_operations = {
195	.open		= pnic_open,
196	.close		= pnic_close,
197	.transmit	= pnic_transmit,
198	.poll		= pnic_poll,
199	.irq   		= pnic_irq,
200};
201
202/**************************************************************************
203DISABLE - Turn off ethernet interface
204***************************************************************************/
205static void pnic_remove ( struct pci_device *pci ) {
206	struct net_device *netdev = pci_get_drvdata ( pci );
207	struct pnic *pnic = netdev->priv;
208
209	unregister_netdev ( netdev );
210	pnic_command ( pnic, PNIC_CMD_RESET, NULL, 0, NULL, 0, NULL );
211	netdev_nullify ( netdev );
212	netdev_put ( netdev );
213}
214
215/**************************************************************************
216PROBE - Look for an adapter, this routine's visible to the outside
217***************************************************************************/
218static int pnic_probe ( struct pci_device *pci,
219			const struct pci_device_id *id __unused ) {
220	struct net_device *netdev;
221	struct pnic *pnic;
222	uint16_t api_version;
223	uint16_t status;
224	int rc;
225
226	/* Allocate net device */
227	netdev = alloc_etherdev ( sizeof ( *pnic ) );
228	if ( ! netdev )
229		return -ENOMEM;
230	netdev_init ( netdev, &pnic_operations );
231	pnic = netdev->priv;
232	pci_set_drvdata ( pci, netdev );
233	netdev->dev = &pci->dev;
234	pnic->ioaddr = pci->ioaddr;
235
236	/* Fix up PCI device */
237	adjust_pci_device ( pci );
238
239	/* API version check */
240	status = pnic_command_quiet ( pnic, PNIC_CMD_API_VER, NULL, 0,
241				      &api_version,
242				      sizeof ( api_version ), NULL );
243	if ( status != PNIC_STATUS_OK ) {
244		printf ( "PNIC failed installation check, code %#hx\n",
245			 status );
246		rc = -EIO;
247		goto err;
248	}
249	pnic_api_check ( api_version );
250
251	/* Get MAC address */
252	status = pnic_command ( pnic, PNIC_CMD_READ_MAC, NULL, 0,
253				netdev->hw_addr, ETH_ALEN, NULL );
254
255	/* Mark as link up; PNIC has no concept of link state */
256	netdev_link_up ( netdev );
257
258	/* Register network device */
259	if ( ( rc = register_netdev ( netdev ) ) != 0 )
260		goto err;
261
262	return 0;
263
264 err:
265	/* Free net device */
266	netdev_nullify ( netdev );
267	netdev_put ( netdev );
268	return rc;
269}
270
271static struct pci_device_id pnic_nics[] = {
272/* genrules.pl doesn't let us use macros for PCI IDs...*/
273PCI_ROM ( 0xfefe, 0xefef, "pnic", "Bochs Pseudo NIC Adaptor", 0 ),
274};
275
276struct pci_driver pnic_driver __pci_driver = {
277	.ids = pnic_nics,
278	.id_count = ( sizeof ( pnic_nics ) / sizeof ( pnic_nics[0] ) ),
279	.probe = pnic_probe,
280	.remove = pnic_remove,
281};
282