vaseboot/VasEBoot-core/net/drivers/ieee1275/ofnet.c

568 lines
15 KiB
C

/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2010,2011 Free Software Foundation, Inc.
*
* VAS_EBOOT 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 3 of the License, or
* (at your option) any later version.
*
* VAS_EBOOT 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 VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/net/netbuff.h>
#include <VasEBoot/ieee1275/ieee1275.h>
#include <VasEBoot/dl.h>
#include <VasEBoot/net.h>
#include <VasEBoot/time.h>
#include <VasEBoot/i18n.h>
#include <VasEBoot/safemath.h>
VAS_EBOOT_MOD_LICENSE ("GPLv3+");
struct VasEBoot_ofnetcard_data
{
char *path;
char *suffix;
VasEBoot_ieee1275_ihandle_t handle;
};
static VasEBoot_err_t
card_open (struct VasEBoot_net_card *dev)
{
int status;
struct VasEBoot_ofnetcard_data *data = dev->data;
status = VasEBoot_ieee1275_open (data->path, &(data->handle));
if (status)
return VasEBoot_error (VAS_EBOOT_ERR_IO, "Couldn't open network card.");
return VAS_EBOOT_ERR_NONE;
}
static void
card_close (struct VasEBoot_net_card *dev)
{
struct VasEBoot_ofnetcard_data *data = dev->data;
if (data->handle)
VasEBoot_ieee1275_close (data->handle);
}
static VasEBoot_err_t
send_card_buffer (struct VasEBoot_net_card *dev, struct VasEBoot_net_buff *pack)
{
VasEBoot_ssize_t actual;
int status;
struct VasEBoot_ofnetcard_data *data = dev->data;
VasEBoot_size_t len;
len = (pack->tail - pack->data);
if (len > dev->mtu)
len = dev->mtu;
VasEBoot_memcpy (dev->txbuf, pack->data, len);
status = VasEBoot_ieee1275_write (data->handle, dev->txbuf,
len, &actual);
if (status)
return VasEBoot_error (VAS_EBOOT_ERR_IO, N_("couldn't send network packet"));
return VAS_EBOOT_ERR_NONE;
}
static struct VasEBoot_net_buff *
get_card_packet (struct VasEBoot_net_card *dev)
{
VasEBoot_ssize_t actual;
int rc;
struct VasEBoot_ofnetcard_data *data = dev->data;
struct VasEBoot_net_buff *nb;
rc = VasEBoot_ieee1275_read (data->handle, dev->rcvbuf, dev->rcvbufsize, &actual);
if (actual <= 0 || rc < 0)
return NULL;
nb = VasEBoot_netbuff_alloc (actual + 2);
if (!nb)
return NULL;
/* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible
by 4. So that IP header is aligned on 4 bytes. */
VasEBoot_netbuff_reserve (nb, 2);
VasEBoot_memcpy (nb->data, dev->rcvbuf, actual);
if (VasEBoot_netbuff_put (nb, actual))
{
VasEBoot_netbuff_free (nb);
return NULL;
}
return nb;
}
static struct VasEBoot_net_card_driver ofdriver =
{
.name = "ofnet",
.open = card_open,
.close = card_close,
.send = send_card_buffer,
.recv = get_card_packet
};
static const struct
{
const char *name;
int offset;
}
bootp_response_properties[] =
{
{ .name = "bootp-response", .offset = 0},
{ .name = "dhcp-response", .offset = 0},
{ .name = "bootpreply-packet", .offset = 0x2a},
};
enum
{
BOOTARGS_SERVER_ADDR,
BOOTARGS_FILENAME,
BOOTARGS_CLIENT_ADDR,
BOOTARGS_GATEWAY_ADDR,
BOOTARGS_BOOTP_RETRIES,
BOOTARGS_TFTP_RETRIES,
BOOTARGS_SUBNET_MASK,
BOOTARGS_BLOCKSIZE
};
static int
VasEBoot_ieee1275_parse_bootpath (const char *devpath, char *bootpath,
char **device, struct VasEBoot_net_card **card)
{
char *args;
char *comma_char = 0;
char *equal_char = 0;
VasEBoot_size_t field_counter = 0;
VasEBoot_net_network_level_address_t client_addr = {0, {0}, 0}, gateway_addr = {0, {0}, 0}, subnet_mask = {0, {0}, 0};
VasEBoot_net_link_level_address_t hw_addr = {0, {{0, 0, 0, 0, 0, 0}}};
VasEBoot_net_interface_flags_t flags = 0;
struct VasEBoot_net_network_level_interface *inter = NULL;
VasEBoot_uint16_t vlantag = 0;
hw_addr.type = VAS_EBOOT_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
args = bootpath + VasEBoot_strlen (devpath) + 1;
do
{
comma_char = VasEBoot_strchr (args, ',');
if (comma_char != 0)
*comma_char = 0;
/* Check if it's an option (like speed=auto) and not a default parameter */
equal_char = VasEBoot_strchr (args, '=');
if (equal_char != 0)
{
*equal_char = 0;
VasEBoot_env_set_net_property ((*card)->name, args, equal_char + 1,
VasEBoot_strlen(equal_char + 1));
if ((VasEBoot_strcmp (args, "vtag") == 0) &&
(VasEBoot_strlen (equal_char + 1) == 8))
vlantag = VasEBoot_strtoul (equal_char + 1 + 4, 0, 16);
*equal_char = '=';
}
else
{
switch (field_counter++)
{
case BOOTARGS_SERVER_ADDR:
*device = VasEBoot_xasprintf ("tftp,%s", args);
if (!*device)
return VasEBoot_errno;
break;
case BOOTARGS_CLIENT_ADDR:
VasEBoot_net_resolve_address (args, &client_addr);
break;
case BOOTARGS_GATEWAY_ADDR:
VasEBoot_net_resolve_address (args, &gateway_addr);
break;
case BOOTARGS_SUBNET_MASK:
VasEBoot_net_resolve_address (args, &subnet_mask);
break;
}
}
args = comma_char + 1;
if (comma_char != 0)
*comma_char = ',';
} while (comma_char != 0);
if ((client_addr.ipv4 != 0) && (subnet_mask.ipv4 != 0))
{
VasEBoot_ieee1275_phandle_t devhandle;
VasEBoot_ieee1275_finddevice (devpath, &devhandle);
VasEBoot_ieee1275_get_property (devhandle, "mac-address",
hw_addr.mac, sizeof(hw_addr.mac), 0);
inter = VasEBoot_net_add_addr ((*card)->name, *card, &client_addr, &hw_addr,
flags);
inter->vlantag = vlantag;
VasEBoot_net_add_ipv4_local (inter,
__builtin_clz (~VasEBoot_be_to_cpu32 (subnet_mask.ipv4)));
}
if (gateway_addr.ipv4 != 0)
{
VasEBoot_net_network_level_netaddress_t target;
char *rname;
target.type = VAS_EBOOT_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
target.ipv4.base = 0;
target.ipv4.masksize = 0;
rname = VasEBoot_xasprintf ("%s:default", ((*card)->name));
if (rname)
VasEBoot_net_add_route_gw (rname, target, gateway_addr, inter);
else
return VasEBoot_errno;
}
return 0;
}
static void
VasEBoot_ieee1275_net_config_real (const char *devpath, char **device, char **path,
char *bootpath)
{
struct VasEBoot_net_card *card;
/* FIXME: Check that it's the right card. */
FOR_NET_CARDS (card)
{
char *bootp_response;
char *canon;
char c;
struct VasEBoot_ofnetcard_data *data;
VasEBoot_ssize_t size = -1;
unsigned int i;
if (card->driver != &ofdriver)
continue;
data = card->data;
c = *data->suffix;
*data->suffix = '\0';
canon = VasEBoot_ieee1275_canonicalise_devname (data->path);
*data->suffix = c;
if (VasEBoot_strcmp (devpath, canon) != 0)
{
VasEBoot_free (canon);
continue;
}
VasEBoot_free (canon);
VasEBoot_ieee1275_parse_bootpath (devpath, bootpath, device, &card);
for (i = 0; i < ARRAY_SIZE (bootp_response_properties); i++)
if (VasEBoot_ieee1275_get_property_length (VasEBoot_ieee1275_chosen,
bootp_response_properties[i].name,
&size) >= 0)
break;
if (size < 0)
return;
bootp_response = VasEBoot_malloc (size);
if (!bootp_response)
{
VasEBoot_print_error ();
return;
}
if (VasEBoot_ieee1275_get_property (VasEBoot_ieee1275_chosen,
bootp_response_properties[i].name,
bootp_response, size, 0) < 0)
return;
VasEBoot_net_configure_by_dhcp_ack (card->name, card, 0,
(struct VasEBoot_net_bootp_packet *)
(bootp_response
+ bootp_response_properties[i].offset),
size - bootp_response_properties[i].offset,
1, device, path);
VasEBoot_free (bootp_response);
return;
}
}
/* Allocate memory with alloc-mem */
static void *
VasEBoot_ieee1275_alloc_mem (VasEBoot_size_t len)
{
struct alloc_args
{
struct VasEBoot_ieee1275_common_hdr common;
VasEBoot_ieee1275_cell_t method;
VasEBoot_ieee1275_cell_t len;
VasEBoot_ieee1275_cell_t catch;
VasEBoot_ieee1275_cell_t result;
}
args;
INIT_IEEE1275_COMMON (&args.common, "interpret", 2, 2);
args.len = len;
args.method = (VasEBoot_ieee1275_cell_t) "alloc-mem";
if (IEEE1275_CALL_ENTRY_FN (&args) == -1 || args.catch)
{
VasEBoot_error (VAS_EBOOT_ERR_INVALID_COMMAND, N_("alloc-mem failed"));
return NULL;
}
else
return (void *)args.result;
}
/* Free memory allocated by alloc-mem */
static VasEBoot_err_t
VasEBoot_ieee1275_free_mem (void *addr, VasEBoot_size_t len)
{
struct free_args
{
struct VasEBoot_ieee1275_common_hdr common;
VasEBoot_ieee1275_cell_t method;
VasEBoot_ieee1275_cell_t len;
VasEBoot_ieee1275_cell_t addr;
VasEBoot_ieee1275_cell_t catch;
}
args;
INIT_IEEE1275_COMMON (&args.common, "interpret", 3, 1);
args.addr = (VasEBoot_ieee1275_cell_t)addr;
args.len = len;
args.method = (VasEBoot_ieee1275_cell_t) "free-mem";
if (IEEE1275_CALL_ENTRY_FN(&args) == -1 || args.catch)
{
VasEBoot_error (VAS_EBOOT_ERR_INVALID_COMMAND, N_("free-mem failed"));
return VasEBoot_errno;
}
return VAS_EBOOT_ERR_NONE;
}
static void *
ofnet_alloc_netbuf (VasEBoot_size_t len)
{
if (VasEBoot_ieee1275_test_flag (VAS_EBOOT_IEEE1275_FLAG_VIRT_TO_REAL_BROKEN))
return VasEBoot_ieee1275_alloc_mem (len);
else
return VasEBoot_zalloc (len);
}
static void
ofnet_free_netbuf (void *addr, VasEBoot_size_t len)
{
if (VasEBoot_ieee1275_test_flag (VAS_EBOOT_IEEE1275_FLAG_VIRT_TO_REAL_BROKEN))
VasEBoot_ieee1275_free_mem (addr, len);
else
VasEBoot_free (addr);
}
static int
search_net_devices (struct VasEBoot_ieee1275_devalias *alias)
{
struct VasEBoot_ofnetcard_data *ofdata;
struct VasEBoot_net_card *card;
VasEBoot_ieee1275_phandle_t devhandle;
VasEBoot_net_link_level_address_t lla;
VasEBoot_ssize_t prop_size;
VasEBoot_uint64_t prop;
VasEBoot_uint8_t *pprop;
char *shortname;
char need_suffix = 1;
VasEBoot_size_t sz;
if (VasEBoot_strcmp (alias->type, "network") != 0)
return 0;
ofdata = VasEBoot_malloc (sizeof (struct VasEBoot_ofnetcard_data));
if (!ofdata)
{
VasEBoot_print_error ();
return 1;
}
card = VasEBoot_zalloc (sizeof (struct VasEBoot_net_card));
if (!card)
{
VasEBoot_free (ofdata);
VasEBoot_print_error ();
return 1;
}
#define SUFFIX ":speed=auto,duplex=auto,1.1.1.1,dummy,1.1.1.1,1.1.1.1,5,5,1.1.1.1,512"
if (VasEBoot_ieee1275_test_flag (VAS_EBOOT_IEEE1275_FLAG_NO_OFNET_SUFFIX))
need_suffix = 0;
/* sun4v vnet devices do not support setting duplex/speed */
{
char *ptr;
VasEBoot_ieee1275_finddevice (alias->path, &devhandle);
VasEBoot_ieee1275_get_property_length (devhandle, "compatible", &prop_size);
if (prop_size > 0)
{
pprop = VasEBoot_malloc (prop_size);
if (!pprop)
{
VasEBoot_free (card);
VasEBoot_free (ofdata);
VasEBoot_print_error ();
return 1;
}
if (!VasEBoot_ieee1275_get_property (devhandle, "compatible",
pprop, prop_size, NULL))
{
for (ptr = (char *) pprop; ptr - (char *) pprop < prop_size;
ptr += VasEBoot_strlen (ptr) + 1)
{
if (!VasEBoot_strcmp(ptr, "SUNW,sun4v-network"))
need_suffix = 0;
}
}
VasEBoot_free (pprop);
}
}
if (need_suffix)
{
if (VasEBoot_add (VasEBoot_strlen (alias->path), sizeof (SUFFIX), &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while obatining size of ofdata path"));
VasEBoot_print_error ();
return 0;
}
}
else
{
if (VasEBoot_add (VasEBoot_strlen (alias->path), 1, &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while obatining size of ofdata path"));
VasEBoot_print_error ();
return 0;
}
}
ofdata->path = VasEBoot_malloc (sz);
if (!ofdata->path)
{
VasEBoot_print_error ();
return 0;
}
ofdata->suffix = VasEBoot_stpcpy (ofdata->path, alias->path);
if (need_suffix)
VasEBoot_memcpy (ofdata->suffix, SUFFIX, sizeof (SUFFIX));
else
*ofdata->suffix = '\0';
VasEBoot_ieee1275_finddevice (ofdata->path, &devhandle);
{
VasEBoot_uint32_t t;
if (VasEBoot_ieee1275_get_integer_property (devhandle,
"max-frame-size", &t,
sizeof (t), 0))
card->mtu = 1500;
else
card->mtu = t;
}
pprop = (VasEBoot_uint8_t *) &prop;
if (VasEBoot_ieee1275_get_property (devhandle, "mac-address",
pprop, sizeof(prop), &prop_size)
&& VasEBoot_ieee1275_get_property (devhandle, "local-mac-address",
pprop, sizeof(prop), &prop_size))
{
VasEBoot_error (VAS_EBOOT_ERR_IO, "Couldn't retrieve mac address.");
VasEBoot_print_error ();
return 0;
}
if (prop_size == 8)
VasEBoot_memcpy (&lla.mac, pprop+2, 6);
else
VasEBoot_memcpy (&lla.mac, pprop, 6);
lla.type = VAS_EBOOT_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
card->default_address = lla;
card->txbufsize = ALIGN_UP (card->mtu, 64) + 256;
card->rcvbufsize = ALIGN_UP (card->mtu, 64) + 256;
card->txbuf = ofnet_alloc_netbuf (card->txbufsize);
if (!card->txbuf)
goto fail_netbuf;
card->rcvbuf = ofnet_alloc_netbuf (card->rcvbufsize);
if (!card->rcvbuf)
{
VasEBoot_error_push ();
ofnet_free_netbuf (card->txbuf, card->txbufsize);
VasEBoot_error_pop ();
goto fail_netbuf;
}
card->driver = NULL;
card->data = ofdata;
card->flags = 0;
shortname = VasEBoot_ieee1275_get_devname (alias->path);
card->name = VasEBoot_xasprintf ("ofnet_%s", shortname ? : alias->path);
card->idle_poll_delay_ms = 10;
VasEBoot_free (shortname);
card->driver = &ofdriver;
VasEBoot_net_card_register (card);
return 0;
fail_netbuf:
VasEBoot_free (ofdata->path);
VasEBoot_free (ofdata);
VasEBoot_free (card);
VasEBoot_print_error ();
return 0;
}
static void
VasEBoot_ofnet_findcards (void)
{
/* Look at all nodes for devices of the type network. */
VasEBoot_ieee1275_devices_iterate (search_net_devices);
}
VAS_EBOOT_MOD_INIT(ofnet)
{
VasEBoot_ofnet_findcards ();
VasEBoot_ieee1275_net_config = VasEBoot_ieee1275_net_config_real;
}
VAS_EBOOT_MOD_FINI(ofnet)
{
struct VasEBoot_net_card *card, *next;
FOR_NET_CARDS_SAFE (card, next)
if (card->driver && VasEBoot_strcmp (card->driver->name, "ofnet") == 0)
VasEBoot_net_card_unregister (card);
VasEBoot_ieee1275_net_config = 0;
}