/* * 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 . */ #include #include #include #include #include #include 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, gateway_addr, subnet_mask; grub_net_link_level_address_t hw_addr; 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 *) ∝ 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; }