Hi Pali, On 11/08/22 00:46, Pali Rohár wrote: > mbus driver is initialized from arch_cpu_init() callback which is called > before relocation. This driver stores lot of functions and structure > pointers into global variables, so it is data position dependent. > > Therefore after relocations all pointers are invalid and driver does not > work anymore as all pointers referes to the old memory, which overlaps with > CONFIG_SYS_LOAD_ADDR and ${loadaddr}. > > For example U-Boot fuse command crashes if loadaddr memory is cleared or > rewritten by some image loaded by U-Boot load command. > > mw.w ${loadaddr} 0x0 10000 > fuse read 0 1 2 > > Fix this issue by removing of all mbus global variables in which are stored > pointers to structures or functions which changes during relocation. And > replace it by direct function calls (not via pointers). With this change > fuse command finally works. > > Signed-off-by: Pali Rohár <p...@kernel.org>
Reviewed-by: Chris Packham <chris.pack...@alliedtelesis.co.nz> > --- > arch/arm/mach-kirkwood/include/mach/cpu.h | 3 - > arch/arm/mach-mvebu/include/mach/cpu.h | 3 - > arch/arm/mach-mvebu/mbus.c | 167 +++++++++------------- > board/alliedtelesis/x530/x530.c | 2 +- > board/maxbcm/maxbcm.c | 8 +- > board/theadorable/theadorable.c | 4 +- > include/linux/mbus.h | 13 +- > 7 files changed, 75 insertions(+), 125 deletions(-) > > diff --git a/arch/arm/mach-kirkwood/include/mach/cpu.h > b/arch/arm/mach-kirkwood/include/mach/cpu.h > index 71c546f9acf6..d8639c60352b 100644 > --- a/arch/arm/mach-kirkwood/include/mach/cpu.h > +++ b/arch/arm/mach-kirkwood/include/mach/cpu.h > @@ -144,9 +144,6 @@ struct kwgpio_registers { > u32 irq_level; > }; > > -/* Needed for dynamic (board-specific) mbus configuration */ > -extern struct mvebu_mbus_state mbus_state; > - > /* > * functions > */ > diff --git a/arch/arm/mach-mvebu/include/mach/cpu.h > b/arch/arm/mach-mvebu/include/mach/cpu.h > index 689c96bd4eac..d9fa1f32aa52 100644 > --- a/arch/arm/mach-mvebu/include/mach/cpu.h > +++ b/arch/arm/mach-mvebu/include/mach/cpu.h > @@ -122,9 +122,6 @@ struct sar_freq_modes { > u32 d_clk; > }; > > -/* Needed for dynamic (board-specific) mbus configuration */ > -extern struct mvebu_mbus_state mbus_state; > - > /* > * functions > */ > diff --git a/arch/arm/mach-mvebu/mbus.c b/arch/arm/mach-mvebu/mbus.c > index 3b1b9f73ebf6..7092f6cc10c2 100644 > --- a/arch/arm/mach-mvebu/mbus.c > +++ b/arch/arm/mach-mvebu/mbus.c > @@ -88,31 +88,34 @@ > > #define DOVE_DDR_BASE_CS_OFF(n) ((n) << 4) > > -struct mvebu_mbus_state; > - > -struct mvebu_mbus_soc_data { > - unsigned int num_wins; > - unsigned int num_remappable_wins; > - unsigned int (*win_cfg_offset)(const int win); > - void (*setup_cpu_target)(struct mvebu_mbus_state *s); > -}; > - > -struct mvebu_mbus_state mbus_state > - __section(".data"); > static struct mbus_dram_target_info mbus_dram_info > __section(".data"); > > +#if defined(CONFIG_ARCH_MVEBU) > + #define MVEBU_MBUS_NUM_WINS 20 > + #define MVEBU_MBUS_NUM_REMAPPABLE_WINS 8 > + #define MVEBU_MBUS_WIN_CFG_OFFSET(win) > armada_370_xp_mbus_win_offset(win) > +#elif defined(CONFIG_ARCH_KIRKWOOD) > + #define MVEBU_MBUS_NUM_WINS 8 > + #define MVEBU_MBUS_NUM_REMAPPABLE_WINS 4 > + #define MVEBU_MBUS_WIN_CFG_OFFSET(win) orion5x_mbus_win_offset(win) > +#else > + #error "No supported architecture" > +#endif > + > +static unsigned int armada_370_xp_mbus_win_offset(int win); > +static unsigned int orion5x_mbus_win_offset(int win); > + > /* > * Functions to manipulate the address decoding windows > */ > > -static void mvebu_mbus_read_window(struct mvebu_mbus_state *mbus, > - int win, int *enabled, u64 *base, > +static void mvebu_mbus_read_window(int win, int *enabled, u64 *base, > u32 *size, u8 *target, u8 *attr, > u64 *remap) > { > - void __iomem *addr = mbus->mbuswins_base + > - mbus->soc->win_cfg_offset(win); > + void __iomem *addr = (void __iomem *)MVEBU_CPU_WIN_BASE + > + MVEBU_MBUS_WIN_CFG_OFFSET(win); > u32 basereg = readl(addr + WIN_BASE_OFF); > u32 ctrlreg = readl(addr + WIN_CTRL_OFF); > > @@ -133,7 +136,7 @@ static void mvebu_mbus_read_window(struct > mvebu_mbus_state *mbus, > *attr = (ctrlreg & WIN_CTRL_ATTR_MASK) >> WIN_CTRL_ATTR_SHIFT; > > if (remap) { > - if (win < mbus->soc->num_remappable_wins) { > + if (win < MVEBU_MBUS_NUM_REMAPPABLE_WINS) { > u32 remap_low = readl(addr + WIN_REMAP_LO_OFF); > u32 remap_hi = readl(addr + WIN_REMAP_HI_OFF); > *remap = ((u64)remap_hi << 32) | remap_low; > @@ -143,27 +146,25 @@ static void mvebu_mbus_read_window(struct > mvebu_mbus_state *mbus, > } > } > > -static void mvebu_mbus_disable_window(struct mvebu_mbus_state *mbus, > - int win) > +static void mvebu_mbus_disable_window(int win) > { > void __iomem *addr; > > - addr = mbus->mbuswins_base + mbus->soc->win_cfg_offset(win); > + addr = (void __iomem *)MVEBU_CPU_WIN_BASE + > MVEBU_MBUS_WIN_CFG_OFFSET(win); > > writel(0, addr + WIN_BASE_OFF); > writel(0, addr + WIN_CTRL_OFF); > - if (win < mbus->soc->num_remappable_wins) { > + if (win < MVEBU_MBUS_NUM_REMAPPABLE_WINS) { > writel(0, addr + WIN_REMAP_LO_OFF); > writel(0, addr + WIN_REMAP_HI_OFF); > } > } > > /* Checks whether the given window number is available */ > -static int mvebu_mbus_window_is_free(struct mvebu_mbus_state *mbus, > - const int win) > +static int mvebu_mbus_window_is_free(const int win) > { > - void __iomem *addr = mbus->mbuswins_base + > - mbus->soc->win_cfg_offset(win); > + void __iomem *addr = (void __iomem *)MVEBU_CPU_WIN_BASE + > + MVEBU_MBUS_WIN_CFG_OFFSET(win); > u32 ctrl = readl(addr + WIN_CTRL_OFF); > return !(ctrl & WIN_CTRL_ENABLE); > } > @@ -172,20 +173,19 @@ static int mvebu_mbus_window_is_free(struct > mvebu_mbus_state *mbus, > * Checks whether the given (base, base+size) area doesn't overlap an > * existing region > */ > -static int mvebu_mbus_window_conflicts(struct mvebu_mbus_state *mbus, > - phys_addr_t base, size_t size, > +static int mvebu_mbus_window_conflicts(phys_addr_t base, size_t size, > u8 target, u8 attr) > { > u64 end = (u64)base + size; > int win; > > - for (win = 0; win < mbus->soc->num_wins; win++) { > + for (win = 0; win < MVEBU_MBUS_NUM_WINS; win++) { > u64 wbase, wend; > u32 wsize; > u8 wtarget, wattr; > int enabled; > > - mvebu_mbus_read_window(mbus, win, > + mvebu_mbus_read_window(win, > &enabled, &wbase, &wsize, > &wtarget, &wattr, NULL); > > @@ -211,17 +211,16 @@ static int mvebu_mbus_window_conflicts(struct > mvebu_mbus_state *mbus, > return 1; > } > > -static int mvebu_mbus_find_window(struct mvebu_mbus_state *mbus, > - phys_addr_t base, size_t size) > +static int mvebu_mbus_find_window(phys_addr_t base, size_t size) > { > int win; > > - for (win = 0; win < mbus->soc->num_wins; win++) { > + for (win = 0; win < MVEBU_MBUS_NUM_WINS; win++) { > u64 wbase; > u32 wsize; > int enabled; > > - mvebu_mbus_read_window(mbus, win, > + mvebu_mbus_read_window(win, > &enabled, &wbase, &wsize, > NULL, NULL, NULL); > > @@ -235,13 +234,12 @@ static int mvebu_mbus_find_window(struct > mvebu_mbus_state *mbus, > return -ENODEV; > } > > -static int mvebu_mbus_setup_window(struct mvebu_mbus_state *mbus, > - int win, phys_addr_t base, size_t size, > +static int mvebu_mbus_setup_window(int win, phys_addr_t base, size_t size, > phys_addr_t remap, u8 target, > u8 attr) > { > - void __iomem *addr = mbus->mbuswins_base + > - mbus->soc->win_cfg_offset(win); > + void __iomem *addr = (void __iomem *)MVEBU_CPU_WIN_BASE + > + MVEBU_MBUS_WIN_CFG_OFFSET(win); > u32 ctrl, remap_addr; > > ctrl = ((size - 1) & WIN_CTRL_SIZE_MASK) | > @@ -251,7 +249,7 @@ static int mvebu_mbus_setup_window(struct > mvebu_mbus_state *mbus, > > writel(base & WIN_BASE_LOW, addr + WIN_BASE_OFF); > writel(ctrl, addr + WIN_CTRL_OFF); > - if (win < mbus->soc->num_remappable_wins) { > + if (win < MVEBU_MBUS_NUM_REMAPPABLE_WINS) { > if (remap == MVEBU_MBUS_NO_REMAP) > remap_addr = base; > else > @@ -263,26 +261,25 @@ static int mvebu_mbus_setup_window(struct > mvebu_mbus_state *mbus, > return 0; > } > > -static int mvebu_mbus_alloc_window(struct mvebu_mbus_state *mbus, > - phys_addr_t base, size_t size, > +static int mvebu_mbus_alloc_window(phys_addr_t base, size_t size, > phys_addr_t remap, u8 target, > u8 attr) > { > int win; > > if (remap == MVEBU_MBUS_NO_REMAP) { > - for (win = mbus->soc->num_remappable_wins; > - win < mbus->soc->num_wins; win++) > - if (mvebu_mbus_window_is_free(mbus, win)) > - return mvebu_mbus_setup_window(mbus, win, base, > + for (win = MVEBU_MBUS_NUM_REMAPPABLE_WINS; > + win < MVEBU_MBUS_NUM_WINS; win++) > + if (mvebu_mbus_window_is_free(win)) > + return mvebu_mbus_setup_window(win, base, > size, remap, > target, attr); > } > > > - for (win = 0; win < mbus->soc->num_wins; win++) > - if (mvebu_mbus_window_is_free(mbus, win)) > - return mvebu_mbus_setup_window(mbus, win, base, size, > + for (win = 0; win < MVEBU_MBUS_NUM_WINS; win++) > + if (mvebu_mbus_window_is_free(win)) > + return mvebu_mbus_setup_window(win, base, size, > remap, target, attr); > > return -ENOMEM; > @@ -292,7 +289,7 @@ static int mvebu_mbus_alloc_window(struct > mvebu_mbus_state *mbus, > * SoC-specific functions and definitions > */ > > -static unsigned int armada_370_xp_mbus_win_offset(int win) > +static unsigned int __maybe_unused armada_370_xp_mbus_win_offset(int win) > { > /* The register layout is a bit annoying and the below code > * tries to cope with it. > @@ -312,12 +309,12 @@ static unsigned int armada_370_xp_mbus_win_offset(int > win) > return 0x90 + ((win - 8) << 3); > } > > -static unsigned int orion5x_mbus_win_offset(int win) > +static unsigned int __maybe_unused orion5x_mbus_win_offset(int win) > { > return win << 4; > } > > -static void mvebu_mbus_default_setup_cpu_target(struct mvebu_mbus_state > *mbus) > +static void mvebu_mbus_default_setup_cpu_target(void) > { > int i; > int cs; > @@ -325,8 +322,8 @@ static void mvebu_mbus_default_setup_cpu_target(struct > mvebu_mbus_state *mbus) > mbus_dram_info.mbus_dram_target_id = TARGET_DDR; > > for (i = 0, cs = 0; i < 4; i++) { > - u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i)); > - u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i)); > + u32 base = readl((void __iomem *)MVEBU_SDRAM_BASE + > DDR_BASE_CS_OFF(i)); > + u32 size = readl((void __iomem *)MVEBU_SDRAM_BASE + > DDR_SIZE_CS_OFF(i)); > > /* > * We only take care of entries for which the chip > @@ -349,26 +346,10 @@ static void mvebu_mbus_default_setup_cpu_target(struct > mvebu_mbus_state *mbus) > > #if defined(CONFIG_ARMADA_MSYS) > /* Disable MBUS Err Prop - in order to avoid data aborts */ > - clrbits_le32(mbus->mbuswins_base + 0x200, BIT(8)); > + clrbits_le32((void __iomem *)MVEBU_CPU_WIN_BASE + 0x200, BIT(8)); > #endif > } > > -static const struct mvebu_mbus_soc_data > -armada_370_xp_mbus_data __maybe_unused = { > - .num_wins = 20, > - .num_remappable_wins = 8, > - .win_cfg_offset = armada_370_xp_mbus_win_offset, > - .setup_cpu_target = mvebu_mbus_default_setup_cpu_target, > -}; > - > -static const struct mvebu_mbus_soc_data > -kirkwood_mbus_data __maybe_unused = { > - .num_wins = 8, > - .num_remappable_wins = 4, > - .win_cfg_offset = orion5x_mbus_win_offset, > - .setup_cpu_target = mvebu_mbus_default_setup_cpu_target, > -}; > - > /* > * Public API of the driver > */ > @@ -382,15 +363,13 @@ int mvebu_mbus_add_window_remap_by_id(unsigned int > target, > phys_addr_t base, size_t size, > phys_addr_t remap) > { > - struct mvebu_mbus_state *s = &mbus_state; > - > - if (!mvebu_mbus_window_conflicts(s, base, size, target, attribute)) { > + if (!mvebu_mbus_window_conflicts(base, size, target, attribute)) { > printf("Cannot add window '%x:%x', conflicts with another > window\n", > target, attribute); > return -EINVAL; > } > > - return mvebu_mbus_alloc_window(s, base, size, remap, target, attribute); > + return mvebu_mbus_alloc_window(base, size, remap, target, attribute); > } > > int mvebu_mbus_add_window_by_id(unsigned int target, unsigned int attribute, > @@ -404,28 +383,27 @@ int mvebu_mbus_del_window(phys_addr_t base, size_t size) > { > int win; > > - win = mvebu_mbus_find_window(&mbus_state, base, size); > + win = mvebu_mbus_find_window(base, size); > if (win < 0) > return win; > > - mvebu_mbus_disable_window(&mbus_state, win); > + mvebu_mbus_disable_window(win); > return 0; > } > > #ifndef CONFIG_ARCH_KIRKWOOD > -static void mvebu_mbus_get_lowest_base(struct mvebu_mbus_state *mbus, > - phys_addr_t *base) > +static void mvebu_mbus_get_lowest_base(phys_addr_t *base) > { > int win; > *base = 0xffffffff; > > - for (win = 0; win < mbus->soc->num_wins; win++) { > + for (win = 0; win < MVEBU_MBUS_NUM_WINS; win++) { > u64 wbase; > u32 wsize; > u8 wtarget, wattr; > int enabled; > > - mvebu_mbus_read_window(mbus, win, > + mvebu_mbus_read_window(win, > &enabled, &wbase, &wsize, > &wtarget, &wattr, NULL); > > @@ -437,14 +415,14 @@ static void mvebu_mbus_get_lowest_base(struct > mvebu_mbus_state *mbus, > } > } > > -static void mvebu_config_mbus_bridge(struct mvebu_mbus_state *mbus) > +static void mvebu_config_mbus_bridge(void) > { > phys_addr_t base; > u32 val; > u32 size; > > /* Set MBUS bridge base/ctrl */ > - mvebu_mbus_get_lowest_base(&mbus_state, &base); > + mvebu_mbus_get_lowest_base(&base); > > size = 0xffffffff - base + 1; > if (!is_power_of_2(size)) { > @@ -461,10 +439,9 @@ static void mvebu_config_mbus_bridge(struct > mvebu_mbus_state *mbus) > } > #endif > > -int mbus_dt_setup_win(struct mvebu_mbus_state *mbus, > - u32 base, u32 size, u8 target, u8 attr) > +int mbus_dt_setup_win(u32 base, u32 size, u8 target, u8 attr) > { > - if (!mvebu_mbus_window_conflicts(mbus, base, size, target, attr)) { > + if (!mvebu_mbus_window_conflicts(base, size, target, attr)) { > printf("Cannot add window '%04x:%04x', conflicts with another > window\n", > target, attr); > return -EBUSY; > @@ -474,8 +451,8 @@ int mbus_dt_setup_win(struct mvebu_mbus_state *mbus, > * In U-Boot we first try to add the mbus window to the remap windows. > * If this fails, lets try to add the windows to the non-remap windows. > */ > - if (mvebu_mbus_alloc_window(mbus, base, size, base, target, attr)) { > - if (mvebu_mbus_alloc_window(mbus, base, size, > + if (mvebu_mbus_alloc_window(base, size, base, target, attr)) { > + if (mvebu_mbus_alloc_window(base, size, > MVEBU_MBUS_NO_REMAP, target, attr)) > return -ENOMEM; > } > @@ -486,7 +463,7 @@ int mbus_dt_setup_win(struct mvebu_mbus_state *mbus, > * is called. Since it may get called from the board code in > * later boot stages as well. > */ > - mvebu_config_mbus_bridge(mbus); > + mvebu_config_mbus_bridge(); > #endif > > return 0; > @@ -498,20 +475,10 @@ int mvebu_mbus_probe(struct mbus_win windows[], int > count) > int ret; > int i; > > -#if defined(CONFIG_ARCH_KIRKWOOD) > - mbus_state.soc = &kirkwood_mbus_data; > -#endif > -#if defined(CONFIG_ARCH_MVEBU) > - mbus_state.soc = &armada_370_xp_mbus_data; > -#endif > - > - mbus_state.mbuswins_base = (void __iomem *)MVEBU_CPU_WIN_BASE; > - mbus_state.sdramwins_base = (void __iomem *)MVEBU_SDRAM_BASE; > - > - for (win = 0; win < mbus_state.soc->num_wins; win++) > - mvebu_mbus_disable_window(&mbus_state, win); > + for (win = 0; win < MVEBU_MBUS_NUM_WINS; win++) > + mvebu_mbus_disable_window(win); > > - mbus_state.soc->setup_cpu_target(&mbus_state); > + mvebu_mbus_default_setup_cpu_target(); > > /* Setup statically declared windows in the DT */ > for (i = 0; i < count; i++) { > @@ -522,7 +489,7 @@ int mvebu_mbus_probe(struct mbus_win windows[], int count) > attr = windows[i].attr; > base = windows[i].base; > size = windows[i].size; > - ret = mbus_dt_setup_win(&mbus_state, base, size, target, attr); > + ret = mbus_dt_setup_win(base, size, target, attr); > if (ret < 0) > return ret; > } > diff --git a/board/alliedtelesis/x530/x530.c b/board/alliedtelesis/x530/x530.c > index cbf4533e78de..0cfb7c522f67 100644 > --- a/board/alliedtelesis/x530/x530.c > +++ b/board/alliedtelesis/x530/x530.c > @@ -109,7 +109,7 @@ int board_init(void) > gd->bd->bi_boot_params = mvebu_sdram_bar(0) + 0x100; > > /* window for NVS */ > - mbus_dt_setup_win(&mbus_state, CONFIG_NVS_LOCATION, CONFIG_NVS_SIZE, > + mbus_dt_setup_win(CONFIG_NVS_LOCATION, CONFIG_NVS_SIZE, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_DEV_CS1); > > /* DEV_READYn is not needed for NVS, ignore it when accessing CS1 */ > diff --git a/board/maxbcm/maxbcm.c b/board/maxbcm/maxbcm.c > index e92132ad19a1..aad3dc864295 100644 > --- a/board/maxbcm/maxbcm.c > +++ b/board/maxbcm/maxbcm.c > @@ -112,13 +112,13 @@ int board_early_init_f(void) > /* > * Setup some board specific mbus address windows > */ > - mbus_dt_setup_win(&mbus_state, DEV_CS0_BASE, 16 << 20, > + mbus_dt_setup_win(DEV_CS0_BASE, 16 << 20, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_DEV_CS0); > - mbus_dt_setup_win(&mbus_state, DEV_CS1_BASE, 16 << 20, > + mbus_dt_setup_win(DEV_CS1_BASE, 16 << 20, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_DEV_CS1); > - mbus_dt_setup_win(&mbus_state, DEV_CS2_BASE, 16 << 20, > + mbus_dt_setup_win(DEV_CS2_BASE, 16 << 20, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_DEV_CS2); > - mbus_dt_setup_win(&mbus_state, DEV_CS3_BASE, 16 << 20, > + mbus_dt_setup_win(DEV_CS3_BASE, 16 << 20, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_DEV_CS3); > > return 0; > diff --git a/board/theadorable/theadorable.c b/board/theadorable/theadorable.c > index 6e41ca239919..144f122bb206 100644 > --- a/board/theadorable/theadorable.c > +++ b/board/theadorable/theadorable.c > @@ -208,9 +208,9 @@ int board_init(void) > * Map SPI devices via MBUS so that they can be accessed via > * the SPI direct access mode > */ > - mbus_dt_setup_win(&mbus_state, SPI_BUS0_DEV1_BASE, SPI_BUS0_DEV1_SIZE, > + mbus_dt_setup_win(SPI_BUS0_DEV1_BASE, SPI_BUS0_DEV1_SIZE, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_SPI0_CS1); > - mbus_dt_setup_win(&mbus_state, SPI_BUS1_DEV2_BASE, SPI_BUS0_DEV1_SIZE, > + mbus_dt_setup_win(SPI_BUS1_DEV2_BASE, SPI_BUS0_DEV1_SIZE, > CPU_TARGET_DEVICEBUS_BOOTROM_SPI, CPU_ATTR_SPI1_CS2); > > /* > diff --git a/include/linux/mbus.h b/include/linux/mbus.h > index 717cbeab37f5..04112eabe1c2 100644 > --- a/include/linux/mbus.h > +++ b/include/linux/mbus.h > @@ -33,16 +33,6 @@ struct mbus_dram_target_info { > } cs[4]; > }; > > -struct mvebu_mbus_state { > - void __iomem *mbuswins_base; > - void __iomem *sdramwins_base; > - struct dentry *debugfs_root; > - struct dentry *debugfs_sdram; > - struct dentry *debugfs_devs; > - const struct mvebu_mbus_soc_data *soc; > - int hw_io_coherency; > -}; > - > /* Flags for PCI/PCIe address decoding regions */ > #define MVEBU_MBUS_PCI_IO 0x1 > #define MVEBU_MBUS_PCI_MEM 0x2 > @@ -67,7 +57,6 @@ int mvebu_mbus_add_window_remap_by_id(unsigned int target, > int mvebu_mbus_add_window_by_id(unsigned int target, unsigned int attribute, > phys_addr_t base, size_t size); > int mvebu_mbus_del_window(phys_addr_t base, size_t size); > -int mbus_dt_setup_win(struct mvebu_mbus_state *mbus, > - u32 base, u32 size, u8 target, u8 attr); > +int mbus_dt_setup_win(u32 base, u32 size, u8 target, u8 attr); > > #endif /* __LINUX_MBUS_H */