From owner-svn-src-all@FreeBSD.ORG Tue Dec 22 21:49:53 2009 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 9D445106568B; Tue, 22 Dec 2009 21:49:53 +0000 (UTC) (envelope-from marius@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 8B7348FC16; Tue, 22 Dec 2009 21:49:53 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.3/8.14.3) with ESMTP id nBMLnrfw077724; Tue, 22 Dec 2009 21:49:53 GMT (envelope-from marius@svn.freebsd.org) Received: (from marius@localhost) by svn.freebsd.org (8.14.3/8.14.3/Submit) id nBMLnr8j077722; Tue, 22 Dec 2009 21:49:53 GMT (envelope-from marius@svn.freebsd.org) Message-Id: <200912222149.nBMLnr8j077722@svn.freebsd.org> From: Marius Strobl Date: Tue, 22 Dec 2009 21:49:53 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r200879 - head/sys/sparc64/ebus X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 22 Dec 2009 21:49:53 -0000 Author: marius Date: Tue Dec 22 21:49:53 2009 New Revision: 200879 URL: http://svn.freebsd.org/changeset/base/200879 Log: - Add support for the JBus to EBus bridges which hang off of nexus(4) and are found in sun4u and sun4v machines based on the Fire ASIC. - Initialize the configuration space of the PCI to EBus variant the same way as OpenSolaris does. Modified: head/sys/sparc64/ebus/ebus.c Modified: head/sys/sparc64/ebus/ebus.c ============================================================================== --- head/sys/sparc64/ebus/ebus.c Tue Dec 22 21:48:18 2009 (r200878) +++ head/sys/sparc64/ebus/ebus.c Tue Dec 22 21:49:53 2009 (r200879) @@ -1,6 +1,7 @@ /*- * Copyright (c) 1999, 2000 Matthew R. Green * Copyright (c) 2001 Thomas Moestl + * Copyright (c) 2009 by Marius Strobl * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +34,7 @@ __FBSDID("$FreeBSD$"); /* - * UltraSPARC 5 and beyond EBus support + * Driver for JBus to EBus and PCI to EBus bridges */ #include @@ -43,14 +44,17 @@ __FBSDID("$FreeBSD$"); #include #include -#include - #include #include #include #include +#include +#ifndef SUN4V +#include +#endif +#include #include #include @@ -59,11 +63,19 @@ __FBSDID("$FreeBSD$"); #include /* - * The register, ranges and interrupt map properties are identical to the ISA - * ones. + * The register, interrupt map and for the PCI variant also the ranges + * properties are identical to the ISA ones. */ #include +struct ebus_nexus_ranges { + uint32_t child_hi; + uint32_t child_lo; + uint32_t phys_hi; + uint32_t phys_lo; + uint32_t size; +}; + struct ebus_devinfo { struct ofw_bus_devinfo edi_obdinfo; struct resource_list edi_rl; @@ -76,32 +88,45 @@ struct ebus_rinfo { }; struct ebus_softc { - struct isa_ranges *sc_range; + void *sc_range; struct ebus_rinfo *sc_rinfo; + u_int sc_flags; +#define EBUS_PCI (1 << 0) + int sc_nrange; struct ofw_bus_iinfo sc_iinfo; + +#ifndef SUN4V + uint32_t sc_ign; +#endif }; -static device_probe_t ebus_probe; -static device_attach_t ebus_attach; +static device_probe_t ebus_nexus_probe; +static device_attach_t ebus_nexus_attach; +static device_probe_t ebus_pci_probe; +static device_attach_t ebus_pci_attach; static bus_print_child_t ebus_print_child; static bus_probe_nomatch_t ebus_probe_nomatch; static bus_alloc_resource_t ebus_alloc_resource; static bus_release_resource_t ebus_release_resource; +static bus_setup_intr_t ebus_setup_intr; static bus_get_resource_list_t ebus_get_resource_list; static ofw_bus_get_devinfo_t ebus_get_devinfo; -static struct ebus_devinfo *ebus_setup_dinfo(device_t, struct ebus_softc *, - phandle_t); -static void ebus_destroy_dinfo(struct ebus_devinfo *); -static int ebus_print_res(struct ebus_devinfo *); +static int ebus_attach(device_t dev, struct ebus_softc *sc, phandle_t node); +static struct ebus_devinfo *ebus_setup_dinfo(device_t dev, + struct ebus_softc *sc, phandle_t node); +static void ebus_destroy_dinfo(struct ebus_devinfo *edi); +static int ebus_print_res(struct ebus_devinfo *edi); + +static devclass_t ebus_devclass; -static device_method_t ebus_methods[] = { +static device_method_t ebus_nexus_methods[] = { /* Device interface */ - DEVMETHOD(device_probe, ebus_probe), - DEVMETHOD(device_attach, ebus_attach), + DEVMETHOD(device_probe, ebus_nexus_probe), + DEVMETHOD(device_attach, ebus_nexus_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), @@ -113,7 +138,7 @@ static device_method_t ebus_methods[] = DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_release_resource, ebus_release_resource), - DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_setup_intr, ebus_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_get_resource_list, ebus_get_resource_list), @@ -130,21 +155,80 @@ static device_method_t ebus_methods[] = KOBJMETHOD_END }; -static driver_t ebus_driver = { +static driver_t ebus_nexus_driver = { "ebus", - ebus_methods, + ebus_nexus_methods, sizeof(struct ebus_softc), }; -static devclass_t ebus_devclass; +/* + * NB: we rely on the interrupt controllers of the accompanying PCI-Express + * bridge to be registered as the nexus variant of the EBus bridges doesn't + * employ its own one. + */ +EARLY_DRIVER_MODULE(ebus, nexus, ebus_nexus_driver, ebus_devclass, 0, 0, + BUS_PASS_BUS + 1); +MODULE_DEPEND(ebus, nexus, 1, 1, 1); + +static device_method_t ebus_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ebus_pci_probe), + DEVMETHOD(device_attach, ebus_pci_attach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), -EARLY_DRIVER_MODULE(ebus, pci, ebus_driver, ebus_devclass, 0, 0, + /* Bus interface */ + DEVMETHOD(bus_print_child, ebus_print_child), + DEVMETHOD(bus_probe_nomatch, ebus_probe_nomatch), + DEVMETHOD(bus_alloc_resource, ebus_alloc_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_release_resource, ebus_release_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + DEVMETHOD(bus_get_resource_list, ebus_get_resource_list), + DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_devinfo, ebus_get_devinfo), + DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), + DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), + DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), + DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), + DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), + + KOBJMETHOD_END +}; + +static driver_t ebus_pci_driver = { + "ebus", + ebus_pci_methods, + sizeof(struct ebus_softc), +}; + +EARLY_DRIVER_MODULE(ebus, pci, ebus_pci_driver, ebus_devclass, 0, 0, BUS_PASS_BUS); MODULE_DEPEND(ebus, pci, 1, 1, 1); MODULE_VERSION(ebus, 1); static int -ebus_probe(device_t dev) +ebus_nexus_probe(device_t dev) +{ + const char* compat; + + compat = ofw_bus_get_compat(dev); + if (compat != NULL && strcmp(ofw_bus_get_name(dev), "ebus") == 0 && + strcmp(compat, "jbus-ebus") == 0) { + device_set_desc(dev, "JBus-EBus bridge"); + return (BUS_PROBE_GENERIC); + } + return (ENXIO); +} + +static int +ebus_pci_probe(device_t dev) { if (pci_get_class(dev) != PCIC_BRIDGE || @@ -158,27 +242,58 @@ ebus_probe(device_t dev) device_set_desc(dev, "PCI-EBus3 bridge"); else return (ENXIO); - return (0); + return (BUS_PROBE_GENERIC); } static int -ebus_attach(device_t dev) +ebus_nexus_attach(device_t dev) +{ + struct ebus_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + +#ifndef SUN4V + if (OF_getprop(node, "portid", &sc->sc_ign, + sizeof(sc->sc_ign)) == -1) { + device_printf(dev, "could not determine IGN"); + return (ENXIO); + } +#endif + + sc->sc_nrange = OF_getprop_alloc(node, "ranges", + sizeof(struct ebus_nexus_ranges), &sc->sc_range); + if (sc->sc_nrange == -1) { + printf("%s: could not get ranges property\n", __func__); + return (ENXIO); + } + return (ebus_attach(dev, sc, node)); +} + +static int +ebus_pci_attach(device_t dev) { struct ebus_softc *sc; - struct ebus_devinfo *edi; struct ebus_rinfo *eri; struct resource *res; - device_t cdev; phandle_t node; int i, rnum, rid; sc = device_get_softc(dev); + sc->sc_flags |= EBUS_PCI; + + pci_write_config(dev, PCIR_COMMAND, + pci_read_config(dev, PCIR_COMMAND, 2) | PCIM_CMD_SERRESPEN | + PCIM_CMD_PERRESPEN | PCIM_CMD_BUSMASTEREN | PCIM_CMD_MEMEN, 2); + pci_write_config(dev, PCIR_CACHELNSZ, 16 /* 64 bytes */, 1); + pci_write_config(dev, PCIR_LATTIMER, 64 /* 64 PCI cycles */, 1); node = ofw_bus_get_node(dev); sc->sc_nrange = OF_getprop_alloc(node, "ranges", - sizeof(*sc->sc_range), (void **)&sc->sc_range); + sizeof(struct isa_ranges), &sc->sc_range); if (sc->sc_nrange == -1) { - printf("ebus_attach: could not get ranges property\n"); + printf("%s: could not get ranges property\n", __func__); return (ENXIO); } @@ -188,29 +303,52 @@ ebus_attach(device_t dev) /* For every range, there must be a matching resource. */ for (rnum = 0; rnum < sc->sc_nrange; rnum++) { eri = &sc->sc_rinfo[rnum]; - eri->eri_rtype = ofw_isa_range_restype(&sc->sc_range[rnum]); + eri->eri_rtype = ofw_isa_range_restype( + &((struct isa_ranges *)sc->sc_range)[rnum]); rid = PCIR_BAR(rnum); res = bus_alloc_resource_any(dev, eri->eri_rtype, &rid, RF_ACTIVE); if (res == NULL) { - printf("ebus_attach: failed to allocate range " - "resource!\n"); + printf("%s: failed to allocate range resource!\n", + __func__); goto fail; } eri->eri_res = res; eri->eri_rman.rm_type = RMAN_ARRAY; eri->eri_rman.rm_descr = "EBus range"; if (rman_init(&eri->eri_rman) != 0) { - printf("ebus_attach: failed to initialize rman!"); + printf("%s: failed to initialize rman!", __func__); goto fail; } if (rman_manage_region(&eri->eri_rman, rman_get_start(res), rman_get_end(res)) != 0) { - printf("ebus_attach: failed to register region!"); + printf("%s: failed to register region!", __func__); rman_fini(&eri->eri_rman); goto fail; } } + return (ebus_attach(dev, sc, node)); + + fail: + for (i = rnum; i >= 0; i--) { + eri = &sc->sc_rinfo[i]; + if (i < rnum) + rman_fini(&eri->eri_rman); + if (eri->eri_res != 0) { + bus_release_resource(dev, eri->eri_rtype, + PCIR_BAR(rnum), eri->eri_res); + } + } + free(sc->sc_rinfo, M_DEVBUF); + free(sc->sc_range, M_OFWPROP); + return (ENXIO); +} + +static int +ebus_attach(device_t dev, struct ebus_softc *sc, phandle_t node) +{ + struct ebus_devinfo *edi; + device_t cdev; ofw_bus_setup_iinfo(node, &sc->sc_iinfo, sizeof(ofw_isa_intr_t)); @@ -229,20 +367,6 @@ ebus_attach(device_t dev) device_set_ivars(cdev, edi); } return (bus_generic_attach(dev)); - -fail: - for (i = rnum; i >= 0; i--) { - eri = &sc->sc_rinfo[i]; - if (i < rnum) - rman_fini(&eri->eri_rman); - if (eri->eri_res != 0) { - bus_release_resource(dev, eri->eri_rtype, - PCIR_BAR(rnum), eri->eri_res); - } - } - free(sc->sc_rinfo, M_DEVBUF); - free(sc->sc_range, M_OFWPROP); - return (ENXIO); } static int @@ -274,28 +398,26 @@ ebus_alloc_resource(device_t bus, device struct resource_list_entry *rle = NULL; struct resource *res; struct ebus_rinfo *ri; + struct ebus_nexus_ranges *enr; bus_space_tag_t bt; bus_space_handle_t bh; - int passthrough = (device_get_parent(child) != bus); - int isdefault = (start == 0UL && end == ~0UL); - int ridx, rv; + uint64_t cend, cstart, offset; + int i, isdefault, passthrough, ridx, rv; - sc = (struct ebus_softc *)device_get_softc(bus); + isdefault = (start == 0UL && end == ~0UL); + passthrough = (device_get_parent(child) != bus); + sc = device_get_softc(bus); rl = BUS_GET_RESOURCE_LIST(bus, child); - /* - * Map EBus ranges to PCI ranges. This may include changing the - * allocation type. - */ switch (type) { case SYS_RES_MEMORY: KASSERT(!(isdefault && passthrough), - ("ebus_alloc_resource: passthrough of default alloc")); + ("%s: passthrough of default allocation", __func__)); if (!passthrough) { rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); KASSERT(rle->res == NULL, - ("ebus_alloc_resource: resource entry is busy")); + ("%s: resource entry is busy", __func__)); if (isdefault) { start = rle->start; count = ulmax(count, rle->count); @@ -303,25 +425,53 @@ ebus_alloc_resource(device_t bus, device } } - (void)ofw_isa_range_map(sc->sc_range, sc->sc_nrange, - &start, &end, &ridx); + res = NULL; + if ((sc->sc_flags & EBUS_PCI) != 0) { + /* + * Map EBus ranges to PCI ranges. This may include + * changing the allocation type. + */ + (void)ofw_isa_range_map(sc->sc_range, sc->sc_nrange, + &start, &end, &ridx); + ri = &sc->sc_rinfo[ridx]; + res = rman_reserve_resource(&ri->eri_rman, start, end, + count, flags, child); + if (res == NULL) + return (NULL); + rman_set_rid(res, *rid); + bt = rman_get_bustag(ri->eri_res); + rman_set_bustag(res, bt); + rv = bus_space_subregion(bt, + rman_get_bushandle(ri->eri_res), + rman_get_start(res) - rman_get_start(ri->eri_res), + count, &bh); + if (rv != 0) { + rman_release_resource(res); + return (NULL); + } + rman_set_bushandle(res, bh); + } else { + /* Map EBus ranges to nexus ranges. */ + for (i = 0; i < sc->sc_nrange; i++) { + enr = &((struct ebus_nexus_ranges *) + sc->sc_range)[i]; + cstart = (((uint64_t)enr->child_hi) << 32) | + enr->child_lo; + cend = cstart + enr->size - 1; + if (start >= cstart && end <= cend) { + offset = + (((uint64_t)enr->phys_hi) << 32) | + enr->phys_lo; + start += offset - cstart; + end += offset - cstart; + res = bus_generic_alloc_resource(bus, + child, type, rid, start, end, + count, flags); + break; + } + } - ri = &sc->sc_rinfo[ridx]; - res = rman_reserve_resource(&ri->eri_rman, start, end, count, - flags, child); - if (res == NULL) - return (NULL); - rman_set_rid(res, *rid); - bt = rman_get_bustag(ri->eri_res); - rman_set_bustag(res, bt); - rv = bus_space_subregion(bt, rman_get_bushandle(ri->eri_res), - rman_get_start(res) - rman_get_start(ri->eri_res), count, - &bh); - if (rv != 0) { - rman_release_resource(res); - return (NULL); } - rman_set_bushandle(res, bh); if (!passthrough) rle->res = res; return (res); @@ -336,34 +486,77 @@ static int ebus_release_resource(device_t bus, device_t child, int type, int rid, struct resource *res) { + struct ebus_softc *sc; struct resource_list *rl; struct resource_list_entry *rle; - int passthrough = (device_get_parent(child) != bus); - int rv; + int passthrough, rv; + passthrough = (device_get_parent(child) != bus); rl = BUS_GET_RESOURCE_LIST(bus, child); switch (type) { case SYS_RES_MEMORY: + sc = device_get_softc(bus); + if ((sc->sc_flags & EBUS_PCI) == 0) + return (resource_list_release(rl, bus, child, type, + rid, res)); if ((rv = rman_release_resource(res)) != 0) return (rv); if (!passthrough) { rle = resource_list_find(rl, type, rid); - KASSERT(rle != NULL, ("ebus_release_resource: " - "resource entry not found!")); - KASSERT(rle->res != NULL, ("ebus_alloc_resource: " - "resource entry is not busy")); + KASSERT(rle != NULL, + ("%s: resource entry not found!", __func__)); + KASSERT(rle->res != NULL, + ("%s: resource entry is not busy", __func__)); rle->res = NULL; } break; case SYS_RES_IRQ: return (resource_list_release(rl, bus, child, type, rid, res)); default: - panic("ebus_release_resource: unsupported resource type %d", - type); + panic("%s: unsupported resource type %d", __func__, type); } return (0); } +static int +ebus_setup_intr(device_t dev, device_t child, struct resource *ires, + int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg, + void **cookiep) +{ +#ifndef SUN4V + struct ebus_softc *sc; + u_long vec; + + sc = device_get_softc(dev); + if ((sc->sc_flags & EBUS_PCI) == 0) { + /* + * Make sure the vector is fully specified. This isn't + * necessarily the case with the PCI variant. + */ + vec = rman_get_start(ires); + if (INTIGN(vec) != sc->sc_ign) { + device_printf(dev, + "invalid interrupt vector 0x%lx\n", vec); + return (EINVAL); + } + + /* + * As we rely on the interrupt controllers of the + * accompanying PCI-Express bridge ensure at least + * something is registered for this vector. + */ + if (intr_vectors[vec].iv_ic == NULL) { + device_printf(dev, + "invalid interrupt controller for vector 0x%lx\n", + vec); + return (EINVAL); + } + } +#endif + return (bus_generic_setup_intr(dev, child, ires, flags, filt, intr, + arg, cookiep)); +} + static struct resource_list * ebus_get_resource_list(device_t dev, device_t child) { @@ -385,12 +578,13 @@ ebus_get_devinfo(device_t bus, device_t static struct ebus_devinfo * ebus_setup_dinfo(device_t dev, struct ebus_softc *sc, phandle_t node) { + struct isa_regs reg, *regs; + ofw_isa_intr_t intr, *intrs; struct ebus_devinfo *edi; - struct isa_regs *reg; - ofw_isa_intr_t *intrs; - ofw_pci_intr_t rintr; - u_int64_t start; - int nreg, nintr, i; + uint64_t start; + uint32_t rintr; + int i, nintr, nreg, rv; + uint8_t maskbuf[sizeof(reg) + sizeof(intr)]; edi = malloc(sizeof(*edi), M_DEVBUF, M_ZERO | M_WAITOK); if (ofw_bus_gen_setup_devinfo(&edi->edi_obdinfo, node) != 0) { @@ -398,40 +592,51 @@ ebus_setup_dinfo(device_t dev, struct eb return (NULL); } resource_list_init(&edi->edi_rl); - nreg = OF_getprop_alloc(node, "reg", sizeof(*reg), (void **)®); + nreg = OF_getprop_alloc(node, "reg", sizeof(*regs), (void **)®s); if (nreg == -1) { device_printf(dev, "<%s>: incomplete\n", edi->edi_obdinfo.obd_name); - goto fail; + ebus_destroy_dinfo(edi); + return (NULL); } for (i = 0; i < nreg; i++) { - start = ISA_REG_PHYS(reg + i); - resource_list_add(&edi->edi_rl, SYS_RES_MEMORY, i, - start, start + reg[i].size - 1, reg[i].size); + start = ISA_REG_PHYS(regs + i); + (void)resource_list_add(&edi->edi_rl, SYS_RES_MEMORY, i, + start, start + regs[i].size - 1, regs[i].size); } - free(reg, M_OFWPROP); + free(regs, M_OFWPROP); nintr = OF_getprop_alloc(node, "interrupts", sizeof(*intrs), (void **)&intrs); + if (nintr == -1) + return (edi); for (i = 0; i < nintr; i++) { - rintr = ofw_isa_route_intr(dev, node, &sc->sc_iinfo, intrs[i]); - if (rintr == PCI_INVALID_IRQ) { + rv = 0; + if ((sc->sc_flags & EBUS_PCI) != 0) { + rintr = ofw_isa_route_intr(dev, node, &sc->sc_iinfo, + intrs[i]); + } else { + intr = intrs[i]; + rv = ofw_bus_lookup_imap(node, &sc->sc_iinfo, ®, + sizeof(reg), &intr, sizeof(intr), &rintr, + sizeof(rintr), maskbuf); +#ifndef SUN4V + if (rv != 0) + rintr = INTMAP_VEC(sc->sc_ign, rintr); +#endif + } + if ((sc->sc_flags & EBUS_PCI) == 0 ? rv == 0 : + rintr == PCI_INVALID_IRQ) { device_printf(dev, "<%s>: could not map EBus interrupt %d\n", edi->edi_obdinfo.obd_name, intrs[i]); - free(intrs, M_OFWPROP); - goto fail; + continue; } - resource_list_add(&edi->edi_rl, SYS_RES_IRQ, i, - rintr, rintr, 1); + (void)resource_list_add(&edi->edi_rl, SYS_RES_IRQ, i, rintr, + rintr, 1); } free(intrs, M_OFWPROP); - return (edi); - -fail: - ebus_destroy_dinfo(edi); - return (NULL); } static void