/* Load the given section: NULL on error. */
static void *PERBIT(load_section)(ElfPERBIT(Ehdr) *hdr,
			    const char *secname,
			    unsigned long *size,
			    int conv)
{
	ElfPERBIT(Shdr) *sechdrs;
	unsigned int i;
	char *secnames;

	/* Grab section headers and strings so we can tell who is who */
	sechdrs = (void *)hdr + END(hdr->e_shoff, conv);
	secnames = (void *)hdr
		+ END(sechdrs[END(hdr->e_shstrndx, conv)].sh_offset, conv);

	/* Find the section they want */
	for (i = 1; i < END(hdr->e_shnum, conv); i++) {
		if (streq(secnames+END(sechdrs[i].sh_name, conv), secname)) {
			*size = END(sechdrs[i].sh_size, conv);
			return (void *)hdr + END(sechdrs[i].sh_offset, conv);
		}
	}
	*size = 0;
	return NULL;
}

static void PERBIT(load_symbols)(struct module *module)
{
	struct PERBIT(kernel_symbol) *ksyms;
	char *ksymstrings;
	unsigned long i, size;

	/* New-style: strings are in this section. */
	ksymstrings = PERBIT(load_section)(module->data, "__ksymtab_strings",
					   &size, module->conv);
	if (ksymstrings) {
		unsigned int i = 0;
		for (;;) {
			/* Skip any zero padding. */
			while (!ksymstrings[i])
				if (++i >= size)
					return;
			add_symbol(ksymstrings+i, module);
			i += strlen(ksymstrings+i);
		}
		/* GPL symbols too */
		ksymstrings = PERBIT(load_section)(module->data,
						   "__ksymtab_strings_gpl",
						   &size, module->conv);
		for (;;) {
			/* Skip any zero padding. */
			while (!ksymstrings[i])
				if (++i >= size)
					return;
			add_symbol(ksymstrings+i, module);
			i += strlen(ksymstrings+i);
		}
		return;
	}

	/* Old-style. */
	ksyms = PERBIT(load_section)(module->data, "__ksymtab", &size,
				     module->conv);
	for (i = 0; i < size / sizeof(struct PERBIT(kernel_symbol)); i++)
		add_symbol(ksyms[i].name, module);
	ksyms = PERBIT(load_section)(module->data, "__gpl_ksymtab", &size,
				     module->conv);
	for (i = 0; i < size / sizeof(struct PERBIT(kernel_symbol)); i++)
		add_symbol(ksyms[i].name, module);
}

static char *PERBIT(get_aliases)(struct module *module, unsigned long *size)
{
	return PERBIT(load_section)(module->data, ".modalias", size,
				    module->conv);
}

static char *PERBIT(get_modinfo)(struct module *module, unsigned long *size)
{
	return PERBIT(load_section)(module->data, ".modinfo", size,
				    module->conv);
}

#ifndef STT_REGISTER
#define STT_REGISTER    13              /* Global register reserved to app. */
#endif

/* Calculate the dependencies for this module */
static void PERBIT(calculate_deps)(struct module *module, int verbose)
{
	unsigned int i;
	unsigned long size;
	char *strings;
	ElfPERBIT(Sym) *syms;
	ElfPERBIT(Ehdr) *hdr;
	int handle_register_symbols;

	strings = PERBIT(load_section)(module->data, ".strtab", &size,
				       module->conv);
	syms = PERBIT(load_section)(module->data, ".symtab", &size,
				    module->conv);

	module->num_deps = 0;
	module->deps = NULL;

	if (!strings || !syms) {
		warn("Couldn't find symtab and strtab in module %s\n",
		     module->pathname);
		return;
	}

	hdr = module->data;
	handle_register_symbols = 0;
	if (END(hdr->e_machine, module->conv) == EM_SPARC ||
	    END(hdr->e_machine, module->conv) == EM_SPARCV9)
		handle_register_symbols = 1;

	for (i = 1; i < size / sizeof(syms[0]); i++) {
		if (END(syms[i].st_shndx, module->conv) == SHN_UNDEF) {
			/* Look for symbol */
			const char *name;
			struct module *owner;
			int weak;

			name = strings + END(syms[i].st_name, module->conv);

			/* Not really undefined: sparc gcc 3.3 creates
                           U references when you have global asm
                           variables, to avoid anyone else misusing
                           them. */
			if (handle_register_symbols
			    && (ELFPERBIT(ST_TYPE)(END(syms[i].st_info,
						       module->conv))
				== STT_REGISTER))
				continue;

			weak = (ELFPERBIT(ST_BIND)(END(syms[i].st_info,
						       module->conv))
				== STB_WEAK);
			owner = find_symbol(name, module->pathname, weak);
			if (owner) {
				if (verbose)
					printf("%s needs \"%s\": %s\n",
					       module->pathname, name,
					       owner->pathname);
				add_dep(module, owner);
			}
		}
	}
}

static void *PERBIT(deref_sym)(ElfPERBIT(Ehdr) *hdr,
			       ElfPERBIT(Shdr) *sechdrs,
			       ElfPERBIT(Sym) *sym,
			       unsigned int *secsize,
			       int conv)
{
	/* In BSS?  Happens for empty device tables on
	 * recent GCC versions. */
	if (END(sechdrs[END(sym->st_shndx, conv)].sh_type,conv) == SHT_NOBITS)
		return NULL;
	
	if (secsize)
		*secsize = END(sym->st_size, conv);
	return (void *)hdr
		+ END(sechdrs[END(sym->st_shndx, conv)].sh_offset, conv)
		+ END(sym->st_value, conv);
}

/* FIXME: Check size, unless we end up using aliases anyway --RR */
static void PERBIT(fetch_tables)(struct module *module)
{
	unsigned int i;
	unsigned long size;
	char *strings;
	ElfPERBIT(Ehdr) *hdr;
	ElfPERBIT(Sym) *syms;
	ElfPERBIT(Shdr) *sechdrs;
	
	hdr = module->data;

	sechdrs = (void *)hdr + END(hdr->e_shoff, module->conv);
	strings = PERBIT(load_section)(hdr, ".strtab", &size, module->conv);
	syms = PERBIT(load_section)(hdr, ".symtab", &size, module->conv);

	/* Don't warn again: we already have above */
	if (!strings || !syms)
		return;
		
	module->pci_table = NULL;
	module->usb_table = NULL;
	module->ccw_table = NULL;
	module->ieee1394_table = NULL;
	module->pnp_table = NULL;
	module->pnp_card_table = NULL;
	module->input_table = NULL;
	module->serio_table = NULL;
	module->of_table = NULL;

	for (i = 0; i < size / sizeof(syms[0]); i++) {
		char *name = strings + END(syms[i].st_name, module->conv);
		
		if (!module->pci_table && streq(name, "__mod_pci_device_table")) {
			module->pci_size = PERBIT(PCI_DEVICE_SIZE);
			module->pci_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, module->conv);
		}
		else if (!module->usb_table && streq(name, "__mod_usb_device_table")) {
			module->usb_size = PERBIT(USB_DEVICE_SIZE);
			module->usb_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, module->conv);
		}
		else if (!module->ccw_table && streq(name, "__mod_ccw_device_table")) {
			module->ccw_size = PERBIT(CCW_DEVICE_SIZE);
			module->ccw_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, module->conv);
		}
		else if (!module->ieee1394_table && streq(name, "__mod_ieee1394_device_table")) {
			module->ieee1394_size = PERBIT(IEEE1394_DEVICE_SIZE);
			module->ieee1394_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
								   NULL, module->conv);
		}
		else if (!module->pnp_table && streq(name, "__mod_pnp_device_table")) {
			module->pnp_size = PERBIT(PNP_DEVICE_SIZE);
			module->pnp_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, module->conv);
		}
		else if (!module->pnp_card_table && streq(name, "__mod_pnp_card_device_table")) {
			module->pnp_card_size = PERBIT(PNP_CARD_DEVICE_SIZE);
			module->pnp_card_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
								   NULL, module->conv);
			module->pnp_card_offset = PERBIT(PNP_CARD_DEVICE_OFFSET);
		}
		else if (!module->input_table && streq(name, "__mod_input_device_table")) {
			module->input_size = PERBIT(INPUT_DEVICE_SIZE);
			module->input_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							        &module->input_table_size,
							        module->conv);
		}
		else if (!module->serio_table && streq(name, "__mod_serio_device_table")) {
			module->serio_size = PERBIT(SERIO_DEVICE_SIZE);
			module->serio_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
								NULL, module->conv);
		}
		else if (!module->of_table && streq(name, "__mod_of_device_table")) {
			module->of_size = PERBIT(OF_DEVICE_SIZE);
			module->of_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							     NULL, module->conv);
		}
	}
}

struct module_ops PERBIT(mod_ops) = {
	.load_symbols	= PERBIT(load_symbols),
	.calculate_deps	= PERBIT(calculate_deps),
	.fetch_tables	= PERBIT(fetch_tables),
	.get_aliases	= PERBIT(get_aliases),
	.get_modinfo	= PERBIT(get_modinfo),
};
