diff options
author | Dale Rahn <drahn@cvs.openbsd.org> | 2006-10-19 03:36:39 +0000 |
---|---|---|
committer | Dale Rahn <drahn@cvs.openbsd.org> | 2006-10-19 03:36:39 +0000 |
commit | bcc4457e46925f5d5fc51c98bc5843df3a9069de (patch) | |
tree | 3fbc5069baa32b52b9dd369e9613deb244b28370 /sys/arch | |
parent | 373006528b6150b2dc236c9c2a6db9bb2b428ae3 (diff) |
pci_address_fixup code to do bus address allocation, 'firmware' appearently
doesn't touch pci. ohci version and re mac address probe correctly.
Diffstat (limited to 'sys/arch')
-rw-r--r-- | sys/arch/sh/conf/files.shpcic | 3 | ||||
-rw-r--r-- | sys/arch/sh/dev/pci_addr_fixup.c | 470 | ||||
-rw-r--r-- | sys/arch/sh/dev/shpcic.c | 15 | ||||
-rw-r--r-- | sys/arch/sh/dev/shpcicvar.h | 27 |
4 files changed, 511 insertions, 4 deletions
diff --git a/sys/arch/sh/conf/files.shpcic b/sys/arch/sh/conf/files.shpcic index bb3e93251b7..1484fcc2c62 100644 --- a/sys/arch/sh/conf/files.shpcic +++ b/sys/arch/sh/conf/files.shpcic @@ -1,4 +1,4 @@ -# $OpenBSD: files.shpcic,v 1.2 2006/10/16 21:46:02 drahn Exp $ +# $OpenBSD: files.shpcic,v 1.3 2006/10/19 03:36:38 drahn Exp $ # $NetBSD: files.shpcic,v 1.2 2005/12/11 12:18:58 christos Exp $ # @@ -8,4 +8,5 @@ device shpcic: pcibus attach shpcic at mainbus file arch/sh/dev/shpcic.c sh4 & shpcic +file arch/sh/dev/pci_addr_fixup.c sh4 & shpcic file arch/sh/dev/pciide_machdep.c pciide diff --git a/sys/arch/sh/dev/pci_addr_fixup.c b/sys/arch/sh/dev/pci_addr_fixup.c new file mode 100644 index 00000000000..d8c4353897e --- /dev/null +++ b/sys/arch/sh/dev/pci_addr_fixup.c @@ -0,0 +1,470 @@ +/* $OpenBSD: pci_addr_fixup.c,v 1.1 2006/10/19 03:36:38 drahn Exp $ */ +/* $NetBSD: pci_addr_fixup.c,v 1.7 2000/08/03 20:10:45 nathanw Exp $ */ + +/*- + * Copyright (c) 2000 UCHIYAMA Yasushi. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/extent.h> + +#include <uvm/uvm_param.h> +#include <machine/bus.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> + +#include <sh/dev/shpcicvar.h> + +typedef int (*pciaddr_resource_manage_func_t)(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t, int, struct extent *, int, bus_addr_t *, + bus_size_t); +void pciaddr_resource_manage(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t, pciaddr_resource_manage_func_t); +void pciaddr_resource_reserve(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t); +void pciaddr_resource_reserve_disabled(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t); +int pciaddr_do_resource_reserve(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t, int, struct extent *, int, + bus_addr_t *, bus_size_t); +int pciaddr_do_resource_reserve_disabled(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t, int, struct extent *, int, + bus_addr_t *, bus_size_t); +void pciaddr_resource_allocate(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t); +int pciaddr_do_resource_allocate(struct shpcic_softc *, + pci_chipset_tag_t, pcitag_t, int, struct extent *, int, bus_addr_t *, + bus_size_t); +bus_addr_t pciaddr_ioaddr(u_int32_t); +void pciaddr_print_devid(pci_chipset_tag_t, pcitag_t); + +int pciaddr_device_is_agp(pci_chipset_tag_t, pcitag_t); + +void pci_device_foreach(struct shpcic_softc *sc, pci_chipset_tag_t pc, + int maxbus, + void (*func)(struct shpcic_softc *, pci_chipset_tag_t, pcitag_t)); + +#define PCIADDR_MEM_START 0x0 +#define PCIADDR_MEM_END 0xffffffff +#define PCIADDR_PORT_START 0x0 +#define PCIADDR_PORT_END 0xffff + +#define PCIBR_VERBOSE 1 +int pcibr_flags = 0; + +#define PCIBIOS_PRINTV(x) if (pcibr_flags & PCIBR_VERBOSE) \ + printf x + +void +pci_addr_fixup(void *v, int maxbus) +{ + struct shpcic_softc *sc = v; + + const char *verbose_header = + "[%s]-----------------------\n" + " device vendor product\n" + " register space address size\n" + "--------------------------------------------\n"; + const char *verbose_footer = + "--------------------------[%3d devices bogus]\n"; + + sc->extent_mem = extent_create("PCI I/O memory space", + sc->sc_membus_space.bus_base, + sc->sc_membus_space.bus_base + sc->sc_membus_space.bus_size, + M_DEVBUF, 0, 0, EX_NOWAIT); + KASSERT(sc->extent_mem); + sc->extent_port = extent_create("PCI I/O port space", + sc->sc_iobus_space.bus_base, + sc->sc_iobus_space.bus_base + sc->sc_iobus_space.bus_size, + M_DEVBUF, 0, 0, EX_NOWAIT); + KASSERT(sc->extent_port); + + /* + * 1. check & reserve system BIOS setting. + */ + PCIBIOS_PRINTV((verbose_header, "System BIOS Setting")); + pci_device_foreach(sc, &sc->sc_pci_chipset, maxbus, + pciaddr_resource_reserve); + pci_device_foreach(sc, &sc->sc_pci_chipset, maxbus, + pciaddr_resource_reserve_disabled); + PCIBIOS_PRINTV((verbose_footer, sc->nbogus)); + + { + struct extent_region *rp; + struct extent *ex = sc->extent_mem; + for (rp = LIST_FIRST(&ex->ex_regions); + rp; rp = LIST_NEXT(rp, er_link)) { + } + } + { + struct extent_region *rp; + struct extent *ex = sc->extent_port; + for (rp = LIST_FIRST(&ex->ex_regions); + rp; rp = LIST_NEXT(rp, er_link)) { + } + } + + /* + * 4. do fixup + */ + PCIBIOS_PRINTV((verbose_header, "PCIBIOS fixup stage")); + sc->nbogus = 0; + pci_device_foreach(sc, &sc->sc_pci_chipset, maxbus, + pciaddr_resource_allocate); + PCIBIOS_PRINTV((verbose_footer, sc->nbogus)); + +} + +void +pciaddr_resource_reserve(struct shpcic_softc *sc, pci_chipset_tag_t pc, + pcitag_t tag) +{ + if (pcibr_flags & PCIBR_VERBOSE) + pciaddr_print_devid(pc, tag); + pciaddr_resource_manage(sc, pc, tag, pciaddr_do_resource_reserve); +} +void +pciaddr_resource_reserve_disabled(struct shpcic_softc *sc, + pci_chipset_tag_t pc, pcitag_t tag) +{ + if (pcibr_flags & PCIBR_VERBOSE) + pciaddr_print_devid(pc, tag); + pciaddr_resource_manage(sc, pc, tag, + pciaddr_do_resource_reserve_disabled); +} + + +void +pciaddr_resource_allocate(struct shpcic_softc *sc, pci_chipset_tag_t pc, + pcitag_t tag) +{ + if (pcibr_flags & PCIBR_VERBOSE) + pciaddr_print_devid(pc, tag); + pciaddr_resource_manage(sc, pc, tag, pciaddr_do_resource_allocate); +} + +void +pciaddr_resource_manage(struct shpcic_softc *sc, pci_chipset_tag_t pc, + pcitag_t tag, pciaddr_resource_manage_func_t func) +{ + struct extent *ex; + pcireg_t val, mask; + bus_addr_t addr; + bus_size_t size; + int error, mapreg, type, reg_start, reg_end, width; + + val = pci_conf_read(pc, tag, PCI_BHLC_REG); + switch (PCI_HDRTYPE_TYPE(val)) { + default: + printf("WARNING: unknown PCI device header.\n"); + sc->nbogus++; + return; + case 0: + reg_start = PCI_MAPREG_START; + reg_end = PCI_MAPREG_END; + break; + case 1: /* PCI-PCI bridge */ + reg_start = PCI_MAPREG_START; + reg_end = PCI_MAPREG_PPB_END; + break; + case 2: /* PCI-CardBus bridge */ + reg_start = PCI_MAPREG_START; + reg_end = PCI_MAPREG_PCB_END; + break; + } + error = 0; + + for (mapreg = reg_start; mapreg < reg_end; mapreg += width) { + /* inquire PCI device bus space requirement */ + val = pci_conf_read(pc, tag, mapreg); + pci_conf_write(pc, tag, mapreg, ~0); + + mask = pci_conf_read(pc, tag, mapreg); + pci_conf_write(pc, tag, mapreg, val); + + type = PCI_MAPREG_TYPE(val); + width = 4; + if (type == PCI_MAPREG_TYPE_MEM) { + if (PCI_MAPREG_MEM_TYPE(val) == + PCI_MAPREG_MEM_TYPE_64BIT) { + /* XXX We could examine the upper 32 bits + * XXX of the BAR here, but we are totally + * XXX unprepared to handle a non-zero value, + * XXX either here or anywhere else in + * XXX i386-land. + * XXX So just arrange to not look at the + * XXX upper 32 bits, lest we misinterpret + * XXX it as a 32-bit BAR set to zero. + */ + width = 8; + } + addr = PCI_MAPREG_MEM_ADDR(val); + size = PCI_MAPREG_MEM_SIZE(mask); + ex = sc->extent_mem; + } else { + /* XXX some devices give 32bit value */ + if (sc->sc_iobus_space.bus_base != PCIADDR_PORT_START) { + /* + * if the bus base is not 0 skew all addresses + */ + val &= PCIADDR_PORT_END; + val |= sc->sc_iobus_space.bus_base; + pci_conf_write(pc, tag, mapreg, val); + } + addr = PCI_MAPREG_IO_ADDR(val); + size = PCI_MAPREG_IO_SIZE(mask); + ex = sc->extent_port; + } + + if (!size) /* unused register */ + continue; + + /* reservation/allocation phase */ + error += (*func) (sc, pc, tag, mapreg, ex, type, &addr, size); + + PCIBIOS_PRINTV(("\t%02xh %s 0x%08x 0x%08x\n", + mapreg, type ? "port" : "mem ", + (unsigned int)addr, (unsigned int)size)); + } + + if (error) + sc->nbogus++; + + PCIBIOS_PRINTV(("\t\t[%s]\n", error ? "NG" : "OK")); +} + +int +pciaddr_do_resource_allocate(struct shpcic_softc *sc, pci_chipset_tag_t pc, + pcitag_t tag, int mapreg, struct extent *ex, int type, bus_addr_t *addr, + bus_size_t size) +{ + bus_addr_t start; + int error; + + if (type == PCI_MAPREG_TYPE_IO) { + if ((*addr & PCIADDR_PORT_END) != 0) + return (0); + } else if (*addr) /* no need to allocate */ + return (0); + + /* XXX Don't allocate if device is AGP device to avoid conflict. */ + if (pciaddr_device_is_agp(pc, tag)) + return (0); + + start = (type == PCI_MAPREG_TYPE_MEM ? sc->sc_membus_space.bus_base + : sc->sc_iobus_space.bus_base); + if (start < ex->ex_start || start + size - 1 >= ex->ex_end) { + PCIBIOS_PRINTV(("No available resources. fixup failed\n")); + return (1); + } + error = extent_alloc_subregion(ex, start, ex->ex_end, size, size, 0, 0, + EX_FAST|EX_NOWAIT|EX_MALLOCOK, addr); + if (error) { + PCIBIOS_PRINTV(("No available resources. fixup failed\n")); + return (1); + } + + /* write new address to PCI device configuration header */ + pci_conf_write(pc, tag, mapreg, *addr); + /* check */ + if (pcibr_flags & PCIBR_VERBOSE) { + printf("pci_addr_fixup: "); + pciaddr_print_devid(pc, tag); + } + + if (pciaddr_ioaddr(pci_conf_read(pc, tag, mapreg)) != *addr) { + pci_conf_write(pc, tag, mapreg, 0); /* clear */ + printf("fixup failed. (new address=%#lx)\n", *addr); + return (1); + } + if (pcibr_flags & PCIBR_VERBOSE) + printf("new address 0x%08lx\n", *addr); + + return (0); +} + +int +pciaddr_do_resource_reserve(struct shpcic_softc *sc, pci_chipset_tag_t pc, + pcitag_t tag, int mapreg, struct extent *ex, int type, bus_addr_t *addr, + bus_size_t size) +{ + pcireg_t val; + int error; + + if ((type == PCI_MAPREG_TYPE_IO) && ((*addr & PCIADDR_PORT_END) == 0)) + return (0); + if (*addr == 0) + return (0); + + val = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + if (type == PCI_MAPREG_TYPE_MEM && + (val & PCI_COMMAND_MEM_ENABLE) != PCI_COMMAND_MEM_ENABLE) + return (0); + if (type == PCI_MAPREG_TYPE_IO && + (val & PCI_COMMAND_IO_ENABLE) != PCI_COMMAND_IO_ENABLE) + return (0); + + error = extent_alloc_region(ex, *addr, size, EX_NOWAIT | EX_MALLOCOK); + if (error) { + PCIBIOS_PRINTV(("Resource conflict.\n")); + pci_conf_write(pc, tag, mapreg, 0); /* clear */ + return (1); + } + + return (0); +} + +int +pciaddr_do_resource_reserve_disabled(struct shpcic_softc *sc, + pci_chipset_tag_t pc, pcitag_t tag, int mapreg, struct extent *ex, + int type, bus_addr_t *addr, bus_size_t size) +{ + pcireg_t val; + int error; + + if ((type == PCI_MAPREG_TYPE_IO) && ((*addr & PCIADDR_PORT_END) == 0)) + return (0); + if (*addr == 0) + return (0); + + val = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + if (type == PCI_MAPREG_TYPE_MEM && + (val & PCI_COMMAND_MEM_ENABLE) == PCI_COMMAND_MEM_ENABLE) + return (0); + if (type == PCI_MAPREG_TYPE_IO && + (val & PCI_COMMAND_IO_ENABLE) == PCI_COMMAND_IO_ENABLE) + return (0); + + error = extent_alloc_region(ex, *addr, size, EX_NOWAIT | EX_MALLOCOK); + if (error) { + PCIBIOS_PRINTV(("Resource conflict.\n")); + pci_conf_write(pc, tag, mapreg, 0); /* clear */ + return (1); + } + + return (0); +} + +bus_addr_t +pciaddr_ioaddr(u_int32_t val) +{ + return ((PCI_MAPREG_TYPE(val) == PCI_MAPREG_TYPE_MEM) + ? PCI_MAPREG_MEM_ADDR(val) + : (PCI_MAPREG_IO_ADDR(val))); +} + +void +pciaddr_print_devid(pci_chipset_tag_t pc, pcitag_t tag) +{ + int bus, device, function; + pcireg_t id; + + id = pci_conf_read(pc, tag, PCI_ID_REG); + pci_decompose_tag(pc, tag, &bus, &device, &function); + printf("%03d:%02d:%d %04x:%04x\n", bus, device, function, + PCI_VENDOR(id), PCI_PRODUCT(id)); +} + +int +pciaddr_device_is_agp(pci_chipset_tag_t pc, pcitag_t tag) +{ + pcireg_t class, status, rval; + int off; + + /* Check AGP device. */ + class = pci_conf_read(pc, tag, PCI_CLASS_REG); + if (PCI_CLASS(class) == PCI_CLASS_DISPLAY) { + status = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + if (status & PCI_STATUS_CAPLIST_SUPPORT) { + rval = pci_conf_read(pc, tag, PCI_CAPLISTPTR_REG); + for (off = PCI_CAPLIST_PTR(rval); + off != 0; + off = PCI_CAPLIST_NEXT(rval) ) { + rval = pci_conf_read(pc, tag, off); + if (PCI_CAPLIST_CAP(rval) == PCI_CAP_AGP) + return (1); + } + } + } + return (0); +} + +void +pci_device_foreach(struct shpcic_softc *sc, pci_chipset_tag_t pc, int maxbus, + void (*func)(struct shpcic_softc *, pci_chipset_tag_t, pcitag_t)) +{ + const struct pci_quirkdata *qd; + int bus, device, function, maxdevs, nfuncs; + pcireg_t id, bhlcr; + pcitag_t tag; + + for (bus = 0; bus <= maxbus; bus++) { + maxdevs = pci_bus_maxdevs(pc, bus); + for (device = 0; device < maxdevs; device++) { + tag = pci_make_tag(pc, bus, device, 0); + id = pci_conf_read(pc, tag, PCI_ID_REG); + + /* Invalid vendor ID value? */ + if (PCI_VENDOR(id) == PCI_VENDOR_INVALID) + continue; + /* XXX Not invalid, but we've done this ~forever. */ + if (PCI_VENDOR(id) == 0) + continue; + + qd = pci_lookup_quirkdata(PCI_VENDOR(id), + PCI_PRODUCT(id)); + + bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG); + if (PCI_HDRTYPE_MULTIFN(bhlcr) || + (qd != NULL && + (qd->quirks & PCI_QUIRK_MULTIFUNCTION) != 0)) + nfuncs = 8; + else + nfuncs = 1; + + for (function = 0; function < nfuncs; function++) { + tag = pci_make_tag(pc, bus, device, function); + id = pci_conf_read(pc, tag, PCI_ID_REG); + + /* Invalid vendor ID value? */ + if (PCI_VENDOR(id) == PCI_VENDOR_INVALID) + continue; + /* + * XXX Not invalid, but we've done this + * ~forever. + */ + if (PCI_VENDOR(id) == 0) + continue; + (*func)(sc, pc, tag); + } + } + } +} diff --git a/sys/arch/sh/dev/shpcic.c b/sys/arch/sh/dev/shpcic.c index 879de522477..411a0d3b561 100644 --- a/sys/arch/sh/dev/shpcic.c +++ b/sys/arch/sh/dev/shpcic.c @@ -1,4 +1,4 @@ -/* $OpenBSD: shpcic.c,v 1.3 2006/10/16 21:28:09 drahn Exp $ */ +/* $OpenBSD: shpcic.c,v 1.4 2006/10/19 03:36:38 drahn Exp $ */ /* $NetBSD: shpcic.c,v 1.10 2005/12/24 20:07:32 perry Exp $ */ /* @@ -68,7 +68,7 @@ int shpcic_match(struct device *, void *, void *); void shpcic_attach(struct device *, struct device *, void *); struct cfattach shpcic_ca = { - sizeof(struct device), shpcic_match, shpcic_attach + sizeof(struct shpcic_softc), shpcic_match, shpcic_attach }; struct cfdriver shpcic_cd = { @@ -137,6 +137,7 @@ void shpcic_attach(struct device *parent, struct device *self, void *aux) { const struct shpcic_product *spp; + struct shpcic_softc *sc = (struct shpcic_softc *)self; struct pcibus_attach_args pba; shpcic_found = 1; @@ -237,6 +238,16 @@ shpcic_attach(struct device *parent, struct device *self, void *aux) intpri_intr_priority(SH4_INTEVT_PCIERR, shpcic_intr_priority[0]); intpri_intr_priority(SH4_INTEVT_PCISERR, shpcic_intr_priority[1]); + sc->sc_membus_space.bus_base = SH4_PCIC_MEM; + sc->sc_membus_space.bus_size = SH4_PCIC_MEM_SIZE; + sc->sc_membus_space.bus_io = 0; + sc->sc_iobus_space.bus_base = SH4_PCIC_IO; /* XXX */ + sc->sc_iobus_space.bus_size = SH4_PCIC_IO_SIZE; + sc->sc_iobus_space.bus_io = 1;; + + sc->sc_pci_chipset = shpcic_get_bus_mem_tag(); + pci_addr_fixup(sc, 1); + /* PCI bus */ memset(&pba, 0, sizeof(pba)); pba.pba_busname = "pci"; diff --git a/sys/arch/sh/dev/shpcicvar.h b/sys/arch/sh/dev/shpcicvar.h index 2e2a2fa8d04..45f001d5f5d 100644 --- a/sys/arch/sh/dev/shpcicvar.h +++ b/sys/arch/sh/dev/shpcicvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: shpcicvar.h,v 1.2 2006/10/07 20:52:40 miod Exp $ */ +/* $OpenBSD: shpcicvar.h,v 1.3 2006/10/19 03:36:38 drahn Exp $ */ /* $NetBSD: shpcicvar.h,v 1.6 2005/12/11 12:18:58 christos Exp $ */ /*- @@ -27,6 +27,9 @@ * SUCH DAMAGE. */ +#ifndef SH_DEV_PCICVAR_H +#define SH_DEV_PCICVAR_H + #include <machine/bus.h> bus_space_tag_t shpcic_get_bus_io_tag(void); @@ -44,6 +47,27 @@ void *shpcic_intr_establish(int evtcode, int (*ih_func)(void *), void *ih_arg, const char *ih_name); void shpcic_intr_disestablish(void *ih); +struct config_bus_space { + u_int32_t bus_base; + u_int32_t bus_size; + int bus_io; +}; + +struct shpcic_softc { + struct device s_dev; + + pci_chipset_tag_t sc_pci_chipset; + + /* Structures to do bus fixup */ + int nbogus; + struct extent *extent_mem; + struct extent *extent_port; + struct config_bus_space sc_membus_space; + struct config_bus_space sc_iobus_space; +}; + +void pci_addr_fixup(void *v, int maxbus); + /* * shpcic io/mem bus space */ @@ -222,3 +246,4 @@ void shpcic_mem_copy_region_2(void *v, bus_space_handle_t bsh1, void shpcic_mem_copy_region_4(void *v, bus_space_handle_t bsh1, bus_size_t off1, bus_space_handle_t bsh2, bus_size_t off2, bus_size_t count); +#endif /* SH_DEV_PCICVAR_H */ |