summaryrefslogtreecommitdiffstats
path: root/grub-core/net/drivers/ieee1275/ofnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/net/drivers/ieee1275/ofnet.c')
-rw-r--r--grub-core/net/drivers/ieee1275/ofnet.c565
1 files changed, 565 insertions, 0 deletions
diff --git a/grub-core/net/drivers/ieee1275/ofnet.c b/grub-core/net/drivers/ieee1275/ofnet.c
new file mode 100644
index 0000000..ac4e62a
--- /dev/null
+++ b/grub-core/net/drivers/ieee1275/ofnet.c
@@ -0,0 +1,565 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2010,2011 Free Software Foundation, Inc.
+ *
+ * GRUB 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.
+ *
+ * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net/netbuff.h>
+#include <grub/ieee1275/ieee1275.h>
+#include <grub/dl.h>
+#include <grub/net.h>
+#include <grub/time.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_ofnetcard_data
+{
+ char *path;
+ char *suffix;
+ grub_ieee1275_ihandle_t handle;
+};
+
+static grub_err_t
+card_open (struct grub_net_card *dev)
+{
+ int status;
+ struct grub_ofnetcard_data *data = dev->data;
+
+ status = grub_ieee1275_open (data->path, &(data->handle));
+
+ if (status)
+ return grub_error (GRUB_ERR_IO, "Couldn't open network card.");
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+card_close (struct grub_net_card *dev)
+{
+ struct grub_ofnetcard_data *data = dev->data;
+
+ if (data->handle)
+ grub_ieee1275_close (data->handle);
+}
+
+static grub_err_t
+send_card_buffer (struct grub_net_card *dev, struct grub_net_buff *pack)
+{
+ grub_ssize_t actual;
+ int status;
+ struct grub_ofnetcard_data *data = dev->data;
+ grub_size_t len;
+
+ len = (pack->tail - pack->data);
+ if (len > dev->mtu)
+ len = dev->mtu;
+
+ grub_memcpy (dev->txbuf, pack->data, len);
+ status = grub_ieee1275_write (data->handle, dev->txbuf,
+ len, &actual);
+
+ if (status)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_net_buff *
+get_card_packet (struct grub_net_card *dev)
+{
+ grub_ssize_t actual;
+ int rc;
+ struct grub_ofnetcard_data *data = dev->data;
+ grub_uint64_t start_time;
+ struct grub_net_buff *nb;
+
+ start_time = grub_get_time_ms ();
+ do
+ rc = grub_ieee1275_read (data->handle, dev->rcvbuf, dev->rcvbufsize, &actual);
+ while ((actual <= 0 || rc < 0) && (grub_get_time_ms () - start_time < 200));
+
+ if (actual <= 0)
+ return NULL;
+
+ nb = grub_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. */
+ grub_netbuff_reserve (nb, 2);
+
+ grub_memcpy (nb->data, dev->rcvbuf, actual);
+
+ if (grub_netbuff_put (nb, actual))
+ {
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+
+ return nb;
+}
+
+static struct grub_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
+grub_ieee1275_parse_bootpath (const char *devpath, char *bootpath,
+ char **device, struct grub_net_card **card)
+{
+ char *args;
+ char *comma_char = 0;
+ char *equal_char = 0;
+ grub_size_t field_counter = 0;
+ grub_net_network_level_address_t client_addr = {0, {0}, 0}, gateway_addr = {0, {0}, 0}, subnet_mask = {0, {0}, 0};
+ grub_net_link_level_address_t hw_addr = {0, {{0, 0, 0, 0, 0, 0}}};
+ grub_net_interface_flags_t flags = 0;
+ struct grub_net_network_level_interface *inter = NULL;
+ grub_uint16_t vlantag = 0;
+
+ hw_addr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+
+ args = bootpath + grub_strlen (devpath) + 1;
+ do
+ {
+ comma_char = grub_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 = grub_strchr (args, '=');
+ if (equal_char != 0)
+ {
+ *equal_char = 0;
+ grub_env_set_net_property ((*card)->name, args, equal_char + 1,
+ grub_strlen(equal_char + 1));
+
+ if ((grub_strcmp (args, "vtag") == 0) &&
+ (grub_strlen (equal_char + 1) == 8))
+ vlantag = grub_strtoul (equal_char + 1 + 4, 0, 16);
+
+ *equal_char = '=';
+ }
+ else
+ {
+ switch (field_counter++)
+ {
+ case BOOTARGS_SERVER_ADDR:
+ *device = grub_xasprintf ("tftp,%s", args);
+ if (!*device)
+ return grub_errno;
+ break;
+
+ case BOOTARGS_CLIENT_ADDR:
+ grub_net_resolve_address (args, &client_addr);
+ break;
+
+ case BOOTARGS_GATEWAY_ADDR:
+ grub_net_resolve_address (args, &gateway_addr);
+ break;
+
+ case BOOTARGS_SUBNET_MASK:
+ grub_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))
+ {
+ grub_ieee1275_phandle_t devhandle;
+ grub_ieee1275_finddevice (devpath, &devhandle);
+ grub_ieee1275_get_property (devhandle, "mac-address",
+ hw_addr.mac, sizeof(hw_addr.mac), 0);
+ inter = grub_net_add_addr ((*card)->name, *card, &client_addr, &hw_addr,
+ flags);
+ inter->vlantag = vlantag;
+ grub_net_add_ipv4_local (inter,
+ __builtin_ctz (~grub_le_to_cpu32 (subnet_mask.ipv4)));
+
+ }
+
+ if (gateway_addr.ipv4 != 0)
+ {
+ grub_net_network_level_netaddress_t target;
+ char *rname;
+
+ target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ target.ipv4.base = 0;
+ target.ipv4.masksize = 0;
+ rname = grub_xasprintf ("%s:default", ((*card)->name));
+ if (rname)
+ grub_net_add_route_gw (rname, target, gateway_addr, inter);
+ else
+ return grub_errno;
+ }
+
+ return 0;
+}
+
+static void
+grub_ieee1275_net_config_real (const char *devpath, char **device, char **path,
+ char *bootpath)
+{
+ struct grub_net_card *card;
+
+ /* FIXME: Check that it's the right card. */
+ FOR_NET_CARDS (card)
+ {
+ char *bootp_response;
+ char *canon;
+ char c;
+ struct grub_ofnetcard_data *data;
+
+ grub_ssize_t size = -1;
+ unsigned int i;
+
+ if (card->driver != &ofdriver)
+ continue;
+
+ data = card->data;
+ c = *data->suffix;
+ *data->suffix = '\0';
+ canon = grub_ieee1275_canonicalise_devname (data->path);
+ *data->suffix = c;
+ if (grub_strcmp (devpath, canon) != 0)
+ {
+ grub_free (canon);
+ continue;
+ }
+ grub_free (canon);
+
+ grub_ieee1275_parse_bootpath (devpath, bootpath, device, &card);
+
+ for (i = 0; i < ARRAY_SIZE (bootp_response_properties); i++)
+ if (grub_ieee1275_get_property_length (grub_ieee1275_chosen,
+ bootp_response_properties[i].name,
+ &size) >= 0)
+ break;
+
+ if (size < 0)
+ return;
+
+ bootp_response = grub_malloc (size);
+ if (!bootp_response)
+ {
+ grub_print_error ();
+ return;
+ }
+ if (grub_ieee1275_get_property (grub_ieee1275_chosen,
+ bootp_response_properties[i].name,
+ bootp_response, size, 0) < 0)
+ return;
+
+ grub_net_configure_by_dhcp_ack (card->name, card, 0,
+ (struct grub_net_bootp_packet *)
+ (bootp_response
+ + bootp_response_properties[i].offset),
+ size - bootp_response_properties[i].offset,
+ 1, device, path);
+ grub_free (bootp_response);
+ return;
+ }
+}
+
+/* Allocate memory with alloc-mem */
+static void *
+grub_ieee1275_alloc_mem (grub_size_t len)
+{
+ struct alloc_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t len;
+ grub_ieee1275_cell_t catch;
+ grub_ieee1275_cell_t result;
+ }
+ args;
+
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_INTERPRET))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_COMMAND, N_("interpret is not supported"));
+ return NULL;
+ }
+
+ INIT_IEEE1275_COMMON (&args.common, "interpret", 2, 2);
+ args.len = len;
+ args.method = (grub_ieee1275_cell_t) "alloc-mem";
+
+ if (IEEE1275_CALL_ENTRY_FN (&args) == -1 || args.catch)
+ {
+ grub_error (GRUB_ERR_INVALID_COMMAND, N_("alloc-mem failed"));
+ return NULL;
+ }
+ else
+ return (void *)args.result;
+}
+
+/* Free memory allocated by alloc-mem */
+static grub_err_t
+grub_ieee1275_free_mem (void *addr, grub_size_t len)
+{
+ struct free_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t len;
+ grub_ieee1275_cell_t addr;
+ grub_ieee1275_cell_t catch;
+ }
+ args;
+
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_INTERPRET))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_COMMAND, N_("interpret is not supported"));
+ return grub_errno;
+ }
+
+ INIT_IEEE1275_COMMON (&args.common, "interpret", 3, 1);
+ args.addr = (grub_ieee1275_cell_t)addr;
+ args.len = len;
+ args.method = (grub_ieee1275_cell_t) "free-mem";
+
+ if (IEEE1275_CALL_ENTRY_FN(&args) == -1 || args.catch)
+ {
+ grub_error (GRUB_ERR_INVALID_COMMAND, N_("free-mem failed"));
+ return grub_errno;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static void *
+ofnet_alloc_netbuf (grub_size_t len)
+{
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_VIRT_TO_REAL_BROKEN))
+ return grub_ieee1275_alloc_mem (len);
+ else
+ return grub_zalloc (len);
+}
+
+static void
+ofnet_free_netbuf (void *addr, grub_size_t len)
+{
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_VIRT_TO_REAL_BROKEN))
+ grub_ieee1275_free_mem (addr, len);
+ else
+ grub_free (addr);
+}
+
+static int
+search_net_devices (struct grub_ieee1275_devalias *alias)
+{
+ struct grub_ofnetcard_data *ofdata;
+ struct grub_net_card *card;
+ grub_ieee1275_phandle_t devhandle;
+ grub_net_link_level_address_t lla;
+ grub_ssize_t prop_size;
+ grub_uint64_t prop;
+ grub_uint8_t *pprop;
+ char *shortname;
+ char need_suffix = 1;
+
+ if (grub_strcmp (alias->type, "network") != 0)
+ return 0;
+
+ ofdata = grub_malloc (sizeof (struct grub_ofnetcard_data));
+ if (!ofdata)
+ {
+ grub_print_error ();
+ return 1;
+ }
+ card = grub_zalloc (sizeof (struct grub_net_card));
+ if (!card)
+ {
+ grub_free (ofdata);
+ grub_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 (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_OFNET_SUFFIX))
+ need_suffix = 0;
+
+ /* sun4v vnet devices do not support setting duplex/speed */
+ {
+ char *ptr;
+
+ grub_ieee1275_finddevice (alias->path, &devhandle);
+
+ grub_ieee1275_get_property_length (devhandle, "compatible", &prop_size);
+ if (prop_size > 0)
+ {
+ pprop = grub_malloc (prop_size);
+ if (!pprop)
+ {
+ grub_free (card);
+ grub_free (ofdata);
+ grub_print_error ();
+ return 1;
+ }
+
+ if (!grub_ieee1275_get_property (devhandle, "compatible",
+ pprop, prop_size, NULL))
+ {
+ for (ptr = (char *) pprop; ptr - (char *) pprop < prop_size;
+ ptr += grub_strlen (ptr) + 1)
+ {
+ if (!grub_strcmp(ptr, "SUNW,sun4v-network"))
+ need_suffix = 0;
+ }
+ }
+
+ grub_free (pprop);
+ }
+ }
+
+ if (need_suffix)
+ ofdata->path = grub_malloc (grub_strlen (alias->path) + sizeof (SUFFIX));
+ else
+ ofdata->path = grub_malloc (grub_strlen (alias->path) + 1);
+ if (!ofdata->path)
+ {
+ grub_print_error ();
+ return 0;
+ }
+ ofdata->suffix = grub_stpcpy (ofdata->path, alias->path);
+ if (need_suffix)
+ grub_memcpy (ofdata->suffix, SUFFIX, sizeof (SUFFIX));
+ else
+ *ofdata->suffix = '\0';
+
+ grub_ieee1275_finddevice (ofdata->path, &devhandle);
+
+ {
+ grub_uint32_t t;
+ if (grub_ieee1275_get_integer_property (devhandle,
+ "max-frame-size", &t,
+ sizeof (t), 0))
+ card->mtu = 1500;
+ else
+ card->mtu = t;
+ }
+
+ pprop = (grub_uint8_t *) &prop;
+ if (grub_ieee1275_get_property (devhandle, "mac-address",
+ pprop, sizeof(prop), &prop_size)
+ && grub_ieee1275_get_property (devhandle, "local-mac-address",
+ pprop, sizeof(prop), &prop_size))
+ {
+ grub_error (GRUB_ERR_IO, "Couldn't retrieve mac address.");
+ grub_print_error ();
+ return 0;
+ }
+
+ if (prop_size == 8)
+ grub_memcpy (&lla.mac, pprop+2, 6);
+ else
+ grub_memcpy (&lla.mac, pprop, 6);
+
+ lla.type = GRUB_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)
+ {
+ grub_error_push ();
+ ofnet_free_netbuf (card->txbuf, card->txbufsize);
+ grub_error_pop ();
+ goto fail_netbuf;
+ }
+ card->driver = NULL;
+ card->data = ofdata;
+ card->flags = 0;
+ shortname = grub_ieee1275_get_devname (alias->path);
+ card->name = grub_xasprintf ("ofnet_%s", shortname ? : alias->path);
+ card->idle_poll_delay_ms = 10;
+ grub_free (shortname);
+
+ card->driver = &ofdriver;
+ grub_net_card_register (card);
+ return 0;
+
+fail_netbuf:
+ grub_free (ofdata->path);
+ grub_free (ofdata);
+ grub_free (card);
+ grub_print_error ();
+ return 0;
+}
+
+static void
+grub_ofnet_findcards (void)
+{
+ /* Look at all nodes for devices of the type network. */
+ grub_ieee1275_devices_iterate (search_net_devices);
+}
+
+GRUB_MOD_INIT(ofnet)
+{
+ grub_ofnet_findcards ();
+ grub_ieee1275_net_config = grub_ieee1275_net_config_real;
+}
+
+GRUB_MOD_FINI(ofnet)
+{
+ struct grub_net_card *card, *next;
+
+ FOR_NET_CARDS_SAFE (card, next)
+ if (card->driver && grub_strcmp (card->driver->name, "ofnet") == 0)
+ grub_net_card_unregister (card);
+ grub_ieee1275_net_config = 0;
+}