| /****************************************************************************** |
| * Copyright (c) 2004, 2007 IBM Corporation |
| * All rights reserved. |
| * This program and the accompanying materials |
| * are made available under the terms of the BSD License |
| * which accompanies this distribution, and is available at |
| * http://www.opensource.org/licenses/bsd-license.php |
| * |
| * Contributors: |
| * IBM Corporation - initial implementation |
| *****************************************************************************/ |
| |
| |
| #include <pci.h> |
| #include <net.h> |
| #include <netdriver_int.h> |
| #include <kernel.h> |
| #include <rtas.h> |
| #include <stdlib.h> |
| #include <of.h> |
| #include <types.h> |
| |
| extern int net_init(pci_config_t * pciconf, char *mac_addr); |
| extern int net_term(pci_config_t * pciconf); |
| extern int net_xmit(pci_config_t * pciconf, char *buffer, int len); |
| extern int net_receive(pci_config_t * pciconf, char *buffer, int len); |
| extern int net_ioctl(pci_config_t * conf, int request, void *data); |
| extern int load_module(const char *, pci_config_t *); |
| |
| |
| static char *mod_names[] = { |
| "net_e1000", |
| "net_bcm", |
| "net_nx203x", |
| "net_mcmal", |
| "net_spider", |
| 0 |
| }; |
| |
| net_driver_t * |
| find_net_driver(pci_config_t pci_conf) |
| { |
| int rc; |
| int i = 0; |
| static net_driver_t module_net_driver; |
| |
| /* Loop over module name */ |
| while (mod_names[i]) { |
| rc = load_module(mod_names[i], &pci_conf); |
| if (rc == 0) { |
| module_net_driver.net_init = net_init; |
| module_net_driver.net_term = net_term; |
| module_net_driver.net_transmit = net_xmit; |
| module_net_driver.net_receive = net_receive; |
| module_net_driver.net_ioctl = net_ioctl; |
| return &module_net_driver; |
| } |
| i++; |
| } |
| return 0; |
| } |
| |
| static phandle_t |
| get_boot_device(void) |
| { |
| static int nameprinted = 1; |
| char buf[1024]; |
| phandle_t dev = of_finddevice("/chosen"); |
| |
| if (dev == -1) { |
| printk("/chosen not found\n"); |
| dev = of_finddevice("/aliases"); |
| if (dev == -1) |
| return dev; |
| printk("/aliases found, net=%s\n", buf); |
| of_getprop(dev, "net", buf, 1024); |
| } else |
| of_getprop(dev, "bootpath", buf, 1024); |
| |
| // FIXME: This printk is nice add to "Reading MAC from device" from netboot |
| // However it is too hackish having "kernel" code finish the application |
| // output line |
| if (!nameprinted++) |
| printk("%s: ", buf); |
| |
| return of_finddevice(buf); |
| } |
| |
| static void |
| get_mac(char *mac) |
| { |
| phandle_t net = get_boot_device(); |
| |
| if (net == -1) |
| return; |
| |
| of_getprop(net, "local-mac-address", mac, 6); |
| } |
| |
| void translate_address_dev(uint64_t *, phandle_t); |
| |
| /** |
| * get_puid walks up in the device tree until it finds a parent |
| * node without a reg property. get_puid is assuming that if the |
| * parent node has no reg property it has found the pci host bridge |
| * |
| * this is not the correct way to find PHBs but it seems to work |
| * for all our systems |
| * |
| * @param node the device for which to find the puid |
| * |
| * @return the puid or 0 |
| */ |
| uint64_t |
| get_puid(phandle_t node) |
| { |
| uint64_t puid = 0; |
| uint64_t tmp = 0; |
| phandle_t curr_node = of_parent(node); |
| if (!curr_node) |
| /* no parent found */ |
| return 0; |
| for (;;) { |
| puid = tmp; |
| if (of_getprop(curr_node, "reg", &tmp, 8) < 8) { |
| /* if the found PHB is not directly under |
| * root we need to translate the found address */ |
| translate_address_dev(&puid, node); |
| return puid; |
| } |
| curr_node = of_parent(curr_node); |
| if (!curr_node) |
| return 0; |
| } |
| return 0; |
| } |
| |
| /* Fill in the pci config structure from the device tree */ |
| int |
| set_pci_config(pci_config_t * pci_config) |
| { |
| unsigned char buf[1024]; |
| int len, bar_nr; |
| unsigned int *assigned_ptr; |
| phandle_t net = get_boot_device(); |
| |
| if (net == -1) |
| return -1; |
| of_getprop(net, "vendor-id", &pci_config->vendor_id, 4); |
| of_getprop(net, "device-id", &pci_config->device_id, 4); |
| of_getprop(net, "revision-id", &pci_config->revision_id, 4); |
| of_getprop(net, "class-code", &pci_config->class_code, 4); |
| of_getprop(net, "interrupts", &pci_config->interrupt_line, 4); |
| |
| len = of_getprop(net, "assigned-addresses", buf, 400); |
| if (len <= 0) |
| return -1; |
| assigned_ptr = (unsigned int *) &buf[0]; |
| pci_config->bus = (assigned_ptr[0] & 0x00ff0000) >> 16; |
| pci_config->devfn = (assigned_ptr[0] & 0x0000ff00) >> 8; |
| |
| while (len > 0) { |
| /* Fixme 64 bit bars */ |
| bar_nr = ((assigned_ptr[0] & 0xff) - 0x10) / 4; |
| pci_config->bars[bar_nr].type = |
| (assigned_ptr[0] & 0x0f000000) >> 24; |
| pci_config->bars[bar_nr].addr = assigned_ptr[2]; |
| pci_config->bars[bar_nr].size = assigned_ptr[4]; |
| assigned_ptr += 5; |
| len -= 5 * sizeof(int); |
| } |
| |
| pci_config->puid = get_puid(net); |
| |
| return 0; |
| } |
| |
| |
| typedef struct { |
| pci_config_t pci_conf; |
| net_driver_t net_driv; |
| |
| char mac_addr[6]; |
| int running; |
| } net_status_t; |
| |
| static net_status_t net_status; |
| |
| int |
| init_net_driver() |
| { |
| int result = 0; |
| net_driver_t *net_driver; |
| |
| /* Find net device and initialize pci config */ |
| if (set_pci_config(&net_status.pci_conf) == -1) { |
| printk(" No net device found \n"); |
| return -1; |
| } |
| |
| /* Get mac address from device tree */ |
| get_mac(net_status.mac_addr); |
| |
| /* Find net device driver */ |
| |
| net_driver = find_net_driver(net_status.pci_conf); |
| if (net_driver == 0) { |
| printk(" No net device driver found \n"); |
| return -1; |
| } |
| net_status.net_driv = *net_driver; |
| /* Init net device driver */ |
| result = net_status.net_driv.net_init(&net_status.pci_conf, |
| &net_status.mac_addr[0]); |
| if (result == -1) { |
| net_status.running = 0; |
| return -2; |
| } else { |
| net_status.running = 1; |
| } |
| return 0; |
| } |
| |
| |
| void |
| term_net_driver() |
| { |
| if (net_status.running == 1) |
| net_status.net_driv.net_term(&net_status.pci_conf); |
| } |
| |
| |
| int |
| _socket(int domain, int type, int proto, char *mac_addr) |
| { |
| static int first_time = 1; |
| if (first_time) { |
| switch (init_net_driver()) { |
| case -1: |
| return -1; |
| case -2: |
| return -2; |
| default: |
| break; |
| } |
| first_time = 0; |
| } |
| |
| memcpy(mac_addr, &net_status.mac_addr[0], 6); |
| return 0; |
| } |
| |
| |
| long |
| _recv(int fd, void *packet, int packet_len, int flags) |
| { |
| return net_status.net_driv.net_receive(&net_status.pci_conf, |
| packet, packet_len); |
| } |
| |
| |
| long |
| _sendto(int fd, void *packet, int packet_len, int flags, |
| void *sock_addr, int sock_addr_len) |
| { |
| return net_status.net_driv.net_transmit(&net_status.pci_conf, |
| packet, packet_len); |
| } |
| |
| long |
| _send(int fd, void *packet, int packet_len, int flags) |
| { |
| return net_status.net_driv.net_transmit(&net_status.pci_conf, |
| packet, packet_len); |
| } |
| |
| long |
| _ioctl(int fd, int request, void *data) |
| { |
| net_driver_t *net_driver; |
| |
| /* Find net device and initialize pci config */ |
| if (set_pci_config(&net_status.pci_conf) == -1) { |
| printk(" No net device found \n"); |
| return -1; |
| } |
| |
| /* Find net device driver */ |
| |
| net_driver = find_net_driver(net_status.pci_conf); |
| if (net_driver == 0) { |
| printk(" No net device driver found \n"); |
| return -1; |
| } |
| net_status.net_driv = *net_driver; |
| |
| return net_status.net_driv.net_ioctl(&net_status.pci_conf, |
| request, data); |
| } |
| |
| |
| void * |
| malloc_aligned(size_t size, int align) |
| { |
| unsigned long p = (unsigned long) malloc(size + align - 1); |
| p = p + align - 1; |
| p = p & ~(align - 1); |
| |
| return (void *) p; |
| } |
| |
| #define CONFIG_SPACE 0 |
| #define IO_SPACE 1 |
| #define MEM_SPACE 2 |
| |
| #define ASSIGNED_ADDRESS_PROPERTY 0 |
| #define REG_PROPERTY 1 |
| |
| #define DEBUG_TRANSLATE_ADDRESS 0 |
| #if DEBUG_TRANSLATE_ADDRESS != 0 |
| #define DEBUG_TR(str...) printk(str) |
| #else |
| #define DEBUG_TR(str...) |
| #endif |
| |
| /** |
| * pci_address_type tries to find the type for which a |
| * mapping should be done. This is PCI specific and is done by |
| * looking at the first 32bit of the phys-addr in |
| * assigned-addresses |
| * |
| * @param node the node of the device which requests |
| * translatation |
| * @param address the address which needs to be translated |
| * @param prop_type the type of the property to search in (either REG_PROPERTY or ASSIGNED_ADDRESS_PROPERTY) |
| * @return the corresponding type (config, i/o, mem) |
| */ |
| static int |
| pci_address_type(phandle_t node, uint64_t address, uint8_t prop_type) |
| { |
| char *prop_name = "assigned-addresses"; |
| if (prop_type == REG_PROPERTY) |
| prop_name = "reg"; |
| /* #address-cells */ |
| const unsigned int nac = 3; //PCI |
| /* #size-cells */ |
| const unsigned int nsc = 2; //PCI |
| /* up to 11 pairs of (phys-addr(3) size(2)) */ |
| unsigned char buf[11 * (nac + nsc) * sizeof(int)]; |
| unsigned int *assigned_ptr; |
| int result = -1; |
| int len; |
| len = of_getprop(node, prop_name, buf, 11 * (nac + nsc) * sizeof(int)); |
| assigned_ptr = (unsigned int *) &buf[0]; |
| while (len > 0) { |
| if ((prop_type == REG_PROPERTY) |
| && ((assigned_ptr[0] & 0xFF) != 0)) { |
| //BARs and Expansion ROM must be in assigned-addresses... so in reg |
| // we only look for those without config space offset set... |
| assigned_ptr += (nac + nsc); |
| len -= (nac + nsc) * sizeof(int); |
| continue; |
| } |
| DEBUG_TR("%s %x size %x\n", prop_name, assigned_ptr[2], |
| assigned_ptr[4]); |
| if (address >= assigned_ptr[2] |
| && address <= assigned_ptr[2] + assigned_ptr[4]) { |
| DEBUG_TR("found a match\n"); |
| result = (assigned_ptr[0] & 0x03000000) >> 24; |
| break; |
| } |
| assigned_ptr += (nac + nsc); |
| len -= (nac + nsc) * sizeof(int); |
| } |
| /* this can only handle 32bit memory space and should be |
| * removed as soon as translations for 64bit are available */ |
| return (result == 3) ? MEM_SPACE : result; |
| } |
| |
| /** |
| * this is a hack which returns the lower 64 bit of any number of cells |
| * all the higher bits will silently discarded |
| * right now this works pretty good as long 64 bit addresses is all we want |
| * |
| * @param addr a pointer to the first address cell |
| * @param nc number of cells addr points to |
| * @return the lower 64 bit to which addr points |
| */ |
| static uint64_t |
| get_dt_address(uint32_t *addr, uint32_t nc) |
| { |
| uint64_t result = 0; |
| while (nc--) |
| result = (result << 32) | *(addr++); |
| return result; |
| } |
| |
| /** |
| * this functions tries to find a mapping for the given address |
| * it assumes that if we have #address-cells == 3 that we are trying |
| * to do a PCI translation |
| * |
| * @param addr a pointer to the address that should be translated |
| * if a translation has been found the address will |
| * be modified |
| * @param type this is required for PCI devices to find the |
| * correct translation |
| * @param ranges this is one "range" containing the translation |
| * information (one range = nac + pnac + nsc) |
| * @param nac the OF property #address-cells |
| * @param nsc the OF property #size-cells |
| * @param pnac the OF property #address-cells from the parent node |
| * @return -1 if no translation was possible; else 0 |
| */ |
| static int |
| map_one_range(uint64_t *addr, int type, uint32_t *ranges, uint32_t nac, |
| uint32_t nsc, uint32_t pnac) |
| { |
| long offset; |
| /* cm - child mapping */ |
| /* pm - parent mapping */ |
| uint64_t cm, size, pm; |
| /* only check for the type if nac == 3 (PCI) */ |
| DEBUG_TR("type %x, nac %x\n", ranges[0], nac); |
| if (((ranges[0] & 0x03000000) >> 24) != type && nac == 3) |
| return -1; |
| /* okay, it is the same type let's see if we find a mapping */ |
| size = get_dt_address(ranges + nac + pnac, nsc); |
| if (nac == 3) /* skip type if PCI */ |
| cm = get_dt_address(ranges + 1, nac - 1); |
| else |
| cm = get_dt_address(ranges, nac); |
| |
| DEBUG_TR("\t\tchild_mapping %lx\n", cm); |
| DEBUG_TR("\t\tsize %lx\n", size); |
| DEBUG_TR("\t\t*address %lx\n", (uint64_t) * addr); |
| if (cm + size <= (uint64_t) * addr || cm > (uint64_t) * addr) |
| /* it is not inside the mapping range */ |
| return -1; |
| /* get the offset */ |
| offset = *addr - cm; |
| /* and add the offset on the parent mapping */ |
| if (pnac == 3) /* skip type if PCI */ |
| pm = get_dt_address(ranges + nac + 1, pnac - 1); |
| else |
| pm = get_dt_address(ranges + nac, pnac); |
| DEBUG_TR("\t\tparent_mapping %lx\n", pm); |
| *addr = pm + offset; |
| DEBUG_TR("\t\t*address %lx\n", *addr); |
| return 0; |
| } |
| |
| /** |
| * translate_address_dev tries to translate the device specific address |
| * to a host specific address by walking up in the device tree |
| * |
| * @param address a pointer to a 64 bit value which will be |
| * translated |
| * @param current_node phandle of the device from which the |
| * translation will be started |
| */ |
| void |
| translate_address_dev(uint64_t *addr, phandle_t current_node) |
| { |
| unsigned char buf[1024]; |
| phandle_t parent; |
| unsigned int pnac; |
| unsigned int nac; |
| unsigned int nsc; |
| int addr_type; |
| int len; |
| unsigned int *ranges; |
| unsigned int one_range; |
| DEBUG_TR("translate address %lx, node: %lx\n", *addr, current_node); |
| of_getprop(current_node, "name", buf, 400); |
| DEBUG_TR("current node: %s\n", buf); |
| addr_type = |
| pci_address_type(current_node, *addr, ASSIGNED_ADDRESS_PROPERTY); |
| if (addr_type == -1) { |
| // check in "reg" property if not found in "assigned-addresses" |
| addr_type = pci_address_type(current_node, *addr, REG_PROPERTY); |
| } |
| DEBUG_TR("address_type %x\n", addr_type); |
| current_node = of_parent(current_node); |
| while (1) { |
| parent = of_parent(current_node); |
| if (!parent) { |
| DEBUG_TR("reached root node...\n"); |
| break; |
| } |
| of_getprop(current_node, "#address-cells", &nac, 4); |
| of_getprop(current_node, "#size-cells", &nsc, 4); |
| of_getprop(parent, "#address-cells", &pnac, 4); |
| one_range = nac + pnac + nsc; |
| len = of_getprop(current_node, "ranges", buf, 400); |
| if (len < 0) { |
| DEBUG_TR("no 'ranges' property; not translatable\n"); |
| return; |
| } |
| ranges = (unsigned int *) &buf[0]; |
| while (len > 0) { |
| if (!map_one_range |
| ((uint64_t *) addr, addr_type, ranges, nac, nsc, |
| pnac)) |
| /* after a successful mapping we stop |
| * going through the ranges */ |
| break; |
| ranges += one_range; |
| len -= one_range * sizeof(int); |
| } |
| DEBUG_TR("address %lx\n", *addr); |
| of_getprop(current_node, "name", buf, 400); |
| DEBUG_TR("current node: %s\n", buf); |
| DEBUG_TR("\t#address-cells: %x\n", nac); |
| DEBUG_TR("\t#size-cells: %x\n", nsc); |
| of_getprop(parent, "name", buf, 400); |
| DEBUG_TR("parent node: %s\n", buf); |
| DEBUG_TR("\t#address-cells: %x\n", pnac); |
| current_node = parent; |
| } |
| } |
| |
| /** |
| * translate_address tries to translate the device specific address |
| * of the boot device to a host specific address |
| * |
| * @param address a pointer to a 64 bit value which will be |
| * translated |
| */ |
| void |
| translate_address(uint64_t *addr) |
| { |
| translate_address_dev(addr, get_boot_device()); |
| } |