From 06b00a25f3753dcbc6ff7fe9c3a774b1620d8b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwenol=C3=A9=20Beauchesne?= Date: Wed, 15 Dec 2004 13:26:00 +0000 Subject: merge ppc64 support from modutils 2.4.26 --- mdk-stage1/insmod-modutils/include/elf_ppc64.h | 88 ++++ mdk-stage1/insmod-modutils/obj/obj_ppc64.c | 602 +++++++++++++++++++++++++ 2 files changed, 690 insertions(+) create mode 100644 mdk-stage1/insmod-modutils/include/elf_ppc64.h create mode 100644 mdk-stage1/insmod-modutils/obj/obj_ppc64.c diff --git a/mdk-stage1/insmod-modutils/include/elf_ppc64.h b/mdk-stage1/insmod-modutils/include/elf_ppc64.h new file mode 100644 index 000000000..4c349d16c --- /dev/null +++ b/mdk-stage1/insmod-modutils/include/elf_ppc64.h @@ -0,0 +1,88 @@ +/* Machine-specific elf macros for the PowerPC64. */ + +#define ELFCLASSM ELFCLASS64 +#define ELFDATAM ELFDATA2MSB + +#define MATCH_MACHINE(x) (x == EM_PPC64) + +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela + +struct obj_file; +extern int ppc64_process_syms (struct obj_file *); +extern Elf64_Addr ppc64_module_base (struct obj_file *); +#define arch_module_base ppc64_module_base + +/* these ought to be eventually available in /usr/include/elf.h */ +#ifndef EM_PPC64 +#define EM_PPC64 21 +#endif + +#ifndef R_PPC64_ADDR64 +#define R_PPC64_ADDR64 38 +#define R_PPC64_ADDR16_HIGHER 39 +#define R_PPC64_ADDR16_HIGHERA 40 +#define R_PPC64_ADDR16_HIGHEST 41 +#define R_PPC64_ADDR16_HIGHESTA 42 +#define R_PPC64_UADDR64 43 +#define R_PPC64_REL64 44 +#define R_PPC64_PLT64 45 +#define R_PPC64_PLTREL64 46 +#define R_PPC64_TOC16 47 +#define R_PPC64_TOC16_LO 48 +#define R_PPC64_TOC16_HI 49 +#define R_PPC64_TOC16_HA 50 +#define R_PPC64_TOC 51 +#define R_PPC64_PLTGOT16 52 +#define R_PPC64_PLTGOT16_LO 53 +#define R_PPC64_PLTGOT16_HI 54 +#define R_PPC64_PLTGOT16_HA 55 +#define R_PPC64_ADDR16_DS 56 +#define R_PPC64_ADDR16_LO_DS 57 +#define R_PPC64_GOT16_DS 58 +#define R_PPC64_GOT16_LO_DS 59 +#define R_PPC64_PLT16_LO_DS 60 +#define R_PPC64_SECTOFF_DS 61 +#define R_PPC64_SECTOFF_LO_DS 62 +#define R_PPC64_TOC16_DS 63 +#define R_PPC64_TOC16_LO_DS 64 +#define R_PPC64_PLTGOT16_DS 65 +#define R_PPC64_PLTGOT16_LO_DS 66 +#define R_PPC64_NONE R_PPC_NONE +#define R_PPC64_ADDR32 R_PPC_ADDR32 +#define R_PPC64_ADDR24 R_PPC_ADDR24 +#define R_PPC64_ADDR16 R_PPC_ADDR16 +#define R_PPC64_ADDR16_LO R_PPC_ADDR16_LO +#define R_PPC64_ADDR16_HI R_PPC_ADDR16_HI +#define R_PPC64_ADDR16_HA R_PPC_ADDR16_HA +#define R_PPC64_ADDR14 R_PPC_ADDR14 +#define R_PPC64_ADDR14_BRTAKEN R_PPC_ADDR14_BRTAKEN +#define R_PPC64_ADDR14_BRNTAKEN R_PPC_ADDR14_BRNTAKEN +#define R_PPC64_REL24 R_PPC_REL24 +#define R_PPC64_REL14 R_PPC_REL14 +#define R_PPC64_REL14_BRTAKEN R_PPC_REL14_BRTAKEN +#define R_PPC64_REL14_BRNTAKEN R_PPC_REL14_BRNTAKEN +#define R_PPC64_GOT16 R_PPC_GOT16 +#define R_PPC64_GOT16_LO R_PPC_GOT16_LO +#define R_PPC64_GOT16_HI R_PPC_GOT16_HI +#define R_PPC64_GOT16_HA R_PPC_GOT16_HA +#define R_PPC64_COPY R_PPC_COPY +#define R_PPC64_GLOB_DAT R_PPC_GLOB_DAT +#define R_PPC64_JMP_SLOT R_PPC_JMP_SLOT +#define R_PPC64_RELATIVE R_PPC_RELATIVE +#define R_PPC64_UADDR32 R_PPC_UADDR32 +#define R_PPC64_UADDR16 R_PPC_UADDR16 +#define R_PPC64_REL32 R_PPC_REL32 +#define R_PPC64_PLT32 R_PPC_PLT32 +#define R_PPC64_PLTREL32 R_PPC_PLTREL32 +#define R_PPC64_PLT16_LO R_PPC_PLT16_LO +#define R_PPC64_PLT16_HI R_PPC_PLT16_HI +#define R_PPC64_PLT16_HA R_PPC_PLT16_HA +#define R_PPC64_SECTOFF R_PPC_SECTOFF +#define R_PPC64_SECTOFF_LO R_PPC_SECTOFF_LO +#define R_PPC64_SECTOFF_HI R_PPC_SECTOFF_HI +#define R_PPC64_SECTOFF_HA R_PPC_SECTOFF_HA +#define R_PPC64_ADDR30 R_PPC_ADDR30 +#define R_PPC64_GNU_VTINHERIT R_PPC_GNU_VTINHERIT +#define R_PPC64_GNU_VTENTRY R_PPC_GNU_VTENTRY +#endif /* R_PPC64_ADDR64 */ diff --git a/mdk-stage1/insmod-modutils/obj/obj_ppc64.c b/mdk-stage1/insmod-modutils/obj/obj_ppc64.c new file mode 100644 index 000000000..df7aa63d2 --- /dev/null +++ b/mdk-stage1/insmod-modutils/obj/obj_ppc64.c @@ -0,0 +1,602 @@ +/* Powerpc64 specific support for Elf loading and relocation. + Copyright 2001 Alan Modra , IBM. + + This file is part of the Linux modutils. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include +#include +#include + +#include +#include +#include +#include /* For ksyms */ + +#define DEBUG_PPC 0 + +typedef struct _ppc64_got_t +{ + struct _ppc64_got_t *next; + Elf64_Addr addend; + unsigned int offset: 16; + int reloc_done: 1; +} ppc64_got_t; + +typedef struct _ppc64_symbol_t +{ + struct obj_symbol root; + struct _ppc64_symbol_t *opd_sym; + ppc64_got_t *got_ent; +} ppc64_symbol_t; + +typedef struct _ppc64_file_t +{ + struct obj_file root; + struct obj_section *toc; + struct obj_section *stub; + char *got; /* where we start adding to .toc */ + Elf64_Addr my_tocbase; /* 0x8000 into .toc */ + Elf64_Addr kernel_base; + Elf64_Addr module_base; +} ppc64_file_t; + +/* Stub for calling a function in the kernel or another module, from a + module. The first insn is patched to load the function .opd entry + address. */ + +static const unsigned char ppc64_stub[] = +{ + 0xe9, 0x82, 0x00, 0x00, /* ld %r12,0(%r2) */ + 0xf8, 0x41, 0x00, 0x28, /* std %r2,40(%r1) */ + 0xe8, 0x0c, 0x00, 0x00, /* ld %r0,0(%r12) */ + 0xe8, 0x4c, 0x00, 0x08, /* ld %r2,8(%r12) */ + 0x7c, 0x09, 0x03, 0xa6, /* mtctr %r0 */ + 0x4e, 0x80, 0x04, 0x20, /* bctr */ +}; + +#define SIZEOF_STUB (sizeof (ppc64_stub)) + +struct obj_file * +arch_new_file (void) +{ + ppc64_file_t *f; + f = xmalloc (sizeof (*f)); + f->toc = NULL; + f->stub = NULL; + f->got = NULL; + f->my_tocbase = 0; + + /* Kernel sits at 0xC000... Modules sit at 0xD000... */ + f->kernel_base = (Elf64_Addr) 0xC << (15*4); + f->module_base = (Elf64_Addr) 0xD << (15*4); + return &f->root; +} + +struct obj_section * +arch_new_section (void) +{ + return xmalloc (sizeof (struct obj_section)); +} + +struct obj_symbol * +arch_new_symbol (void) +{ + ppc64_symbol_t *sym; + sym = xmalloc (sizeof (*sym)); + sym->opd_sym = NULL; + sym->got_ent = NULL; + return &sym->root; +} + +int +arch_load_proc_section (struct obj_section *sec, int fp) +{ + /* Assume it's just a debugging section that we can safely + ignore ... */ + sec->contents = NULL; + + return 0; +} + +Elf64_Addr +ppc64_module_base (struct obj_file *f) +{ + return ((ppc64_file_t *) f)->module_base; +} + +static inline Elf64_Addr +ppc64_adjust_symval (ppc64_file_t *f, ppc64_symbol_t *sym, Elf64_Addr v) +{ + /* The kernel doesn't return high 32 bits of sym values as we are + using 32 bit userspace. Adjust sym values as appropriate. We + "or" in the high bits rather than adding so this code will still + work if we eventually have 64 bit userspace. */ + + if (sym->root.secidx == SHN_HIRESERVE + 1) + { + /* It's a kernel sym. */ + v |= f->kernel_base; + } + else if (sym->root.secidx > SHN_HIRESERVE + 1) + { + /* Sym from another module. */ + v |= f->module_base; + } + return v; +} + +int +ppc64_process_syms (struct obj_file *f) +{ + unsigned long i; + ppc64_symbol_t *sym; + ppc64_file_t *ef = (ppc64_file_t *) f; + + if (DEBUG_PPC > 0) + fprintf (stderr, "ppc64_process_syms\n"); + for (i = 0; i < HASH_BUCKETS; ++i) + for (sym = (ppc64_symbol_t *) f->symtab[i]; + sym != NULL; + sym = (ppc64_symbol_t *) sym->root.next) + { + /* The kernel doesn't export function code syms (which all + start with `.'). Instead you get the .opd section + function descriptor. Tie the module reference to the + code sym used in a "bl" instruction, to the opd sym. */ + if (sym->root.name[0] == '.' + && sym->root.secidx == SHN_UNDEF) + { + sym->opd_sym = ((ppc64_symbol_t *) + obj_find_symbol (f, sym->root.name + 1)); + if (sym->opd_sym != NULL) + { + if (sym->opd_sym->root.secidx < SHN_HIRESERVE + 1) + { + /* Whoa. Don't satisfy with a sym from this + module. */ + sym->opd_sym = NULL; + } + else + { + /* It's not really defined here. This is just to + prevent undefined sym errors. */ + sym->root.secidx = sym->opd_sym->root.secidx; + } + } + else if (DEBUG_PPC > 0) + fprintf (stderr, "can't find opd symbol for %s\n", + sym->root.name); + } + } + + sym = (ppc64_symbol_t *) obj_find_symbol (f, "__kernel_base_hi"); + if (sym != NULL && sym->root.secidx != SHN_UNDEF) + ef->kernel_base = (Elf64_Addr) sym->root.value << 32; + + sym = (ppc64_symbol_t *) obj_find_symbol (f, "__kernel_base_lo"); + if (sym != NULL && sym->root.secidx != SHN_UNDEF) + { + ef->kernel_base &= ~(Elf64_Addr) 0xffffffff; + ef->kernel_base |= (Elf64_Addr) sym->root.value & 0xffffffff; + } + + sym = (ppc64_symbol_t *) obj_find_symbol (f, "__module_base_hi"); + if (sym != NULL && sym->root.secidx != SHN_UNDEF) + ef->module_base = (Elf64_Addr) sym->root.value << 32; + + sym = (ppc64_symbol_t *) obj_find_symbol (f, "__module_base_lo"); + if (sym != NULL && sym->root.secidx != SHN_UNDEF) + { + ef->module_base &= ~(Elf64_Addr) 0xffffffff; + ef->module_base |= (Elf64_Addr) sym->root.value & 0xffffffff; + } + + return 1; +} + +int +arch_create_got (struct obj_file *f) +{ + ppc64_file_t *hfile = (ppc64_file_t *) f; + int i; + int n; + unsigned int got_offset; + + got_offset = 0; + + if (DEBUG_PPC > 0) + fprintf (stderr, "arch_create_got\n"); + + n = hfile->root.header.e_shnum; + for (i = 0; i < n; ++i) + { + struct obj_section *relsec, *symsec, *strsec; + Elf64_Rela *rel, *relend; + Elf64_Sym *symtab; + const char *strtab; + + relsec = hfile->root.sections[i]; + if (relsec->header.sh_type != SHT_RELA) + continue; + + symsec = hfile->root.sections[relsec->header.sh_link]; + strsec = hfile->root.sections[symsec->header.sh_link]; + symtab = (Elf64_Sym *) symsec->contents; + strtab = (const char *) strsec->contents; + + rel = (Elf64_Rela *) relsec->contents; + relend = rel + relsec->header.sh_size / sizeof (Elf64_Rela); + for (; rel < relend; ++rel) + { + switch (ELF64_R_TYPE (rel->r_info)) + { + default: + { + unsigned r_info = ELF64_R_TYPE (rel->r_info); + error ("r_info 0x%x not handled\n", r_info); + } + continue; + + case R_PPC64_ADDR16_HA: + case R_PPC64_ADDR16_HI: + case R_PPC64_ADDR16_LO: + case R_PPC64_ADDR16_LO_DS: + case R_PPC64_ADDR32: + case R_PPC64_ADDR64: + case R_PPC64_TOC: + case R_PPC64_TOC16: + case R_PPC64_TOC16_DS: + case R_PPC64_REL14: + case R_PPC64_NONE: + continue; + + case R_PPC64_REL24: + { + Elf64_Sym *extsym; + ppc64_symbol_t *isym; + const char *name; + unsigned long symndx; + ppc64_got_t *got; + + symndx = ELF64_R_SYM (rel->r_info); + extsym = &symtab[symndx]; + if (ELF64_ST_BIND (extsym->st_info) == STB_LOCAL) + { + isym = (ppc64_symbol_t *) f->local_symtab[symndx]; + } + else + { + if (extsym->st_name) + name = strtab + extsym->st_name; + else + name = f->sections[extsym->st_shndx]->name; + isym = (ppc64_symbol_t *) obj_find_symbol (f, name); + + if (DEBUG_PPC > 1) + fprintf (stderr, + "create_got %s + 0x%llx opd_sym=%p secidx=%x\n", + name, + (unsigned long long) rel->r_addend, + isym->opd_sym, + isym->opd_sym ? isym->opd_sym->root.secidx : 0); + + if (isym->opd_sym != NULL + && isym->opd_sym->root.secidx != SHN_UNDEF) + { + for (got = isym->got_ent; got != NULL; got = got->next) + if (got->addend == rel->r_addend) + break; + if (got == NULL) + { + if (DEBUG_PPC > 1) + fprintf (stderr, "making got\n"); + got = (ppc64_got_t *) xmalloc (sizeof (ppc64_got_t)); + got->next = isym->got_ent; + got->addend = rel->r_addend; + got->offset = got_offset; + got->reloc_done = 0; + isym->got_ent = got; + got_offset += 8; + } + else if (DEBUG_PPC > 1) + fprintf (stderr, "got a got\n"); + } + } + } + } + } + } + + hfile->toc = obj_find_section (f, ".toc"); + + if (got_offset != 0) + { + unsigned int stub_size; + + if (hfile->toc) + { + if (hfile->toc->header.sh_size + got_offset > 0xffff) + { + error (".toc section overflow (%lu)\n", + (unsigned long) hfile->toc->header.sh_size + got_offset); + return 0; + } + hfile->got = obj_extend_section (hfile->toc, got_offset); + } + else + { + if (got_offset > 0xffff) + { + error (".toc section overflow (%lu)\n", + (unsigned long) got_offset); + return 0; + } + hfile->toc = obj_create_alloced_section (f, ".toc", 8, got_offset, + SHF_ALLOC); + hfile->got = hfile->toc->contents; + } + + stub_size = got_offset / 8 * SIZEOF_STUB; + hfile->stub = obj_create_alloced_section (f, ".stub", 4, stub_size, + SHF_ALLOC | SHF_EXECINSTR); + } + + return 1; +} + +int +arch_finalize_section_address (struct obj_file *f, Elf64_Addr base) +{ + ppc64_file_t *hfile = (ppc64_file_t *) f; + int n = f->header.e_shnum; + int i; + + f->baseaddr = base; + for (i = 0; i < n; ++i) + f->sections[i]->header.sh_addr += base; + + if (hfile->toc) + { + hfile->my_tocbase = hfile->toc->header.sh_addr + 0x8000; + if (DEBUG_PPC > 0) + fprintf (stderr, "hfile->my_tocbase = %llx\n", + (unsigned long long) hfile->my_tocbase); + } + + return 1; +} + +static inline int apply_addr32 (char *loc, Elf64_Addr v) +{ + *((Elf64_Word *) loc) = (Elf64_Word) v; + return v + ((Elf64_Addr) 1 << 32) >= ((Elf64_Addr) 2 << 32); +} + +static inline int apply_addr16_lo (char *loc, Elf64_Addr v) +{ + *((Elf64_Half *) loc) = (*((Elf64_Half *) loc) & ~0xffff) | (v & 0xffff); + return 0; +} + +static inline int apply_addr16_lo_ds (char *loc, Elf64_Addr v) +{ + *((Elf64_Half *) loc) = (*((Elf64_Half *) loc) & ~0xfffc) | (v & 0xfffc); + return (v & 3) != 0; +} + +static inline int apply_addr16_hi (char *loc, Elf64_Addr v) +{ + return apply_addr16_lo (loc, v >> 16); +} + +static inline int apply_addr16_ha (char *loc, Elf64_Addr v) +{ + return apply_addr16_hi (loc, v + 0x8000); +} + +static inline int apply_toc16 (char *loc, Elf64_Addr v) +{ + return apply_addr16_lo (loc, v) || v + 0x8000 > 0xffff; +} + +static inline int apply_toc16_ds (char *loc, Elf64_Addr v) +{ + return apply_addr16_lo_ds (loc, v) || v + 0x8000 > 0xffff; +} + +enum obj_reloc +arch_apply_relocation (struct obj_file *f, + struct obj_section *targsec, + struct obj_section *symsec, + struct obj_symbol *sym, + Elf64_Rela *rel, + Elf64_Addr v) +{ + ppc64_file_t *hfile = (ppc64_file_t *) f; + ppc64_symbol_t *isym = (ppc64_symbol_t *) sym; + ppc64_got_t *ge; + char *loc = targsec->contents + rel->r_offset; + Elf64_Addr dot; + Elf64_Xword r_info = ELF64_R_TYPE (rel->r_info); + enum obj_reloc ret = obj_reloc_ok; + + switch (r_info) + { + default: + ret = obj_reloc_unhandled; + break; + + case R_PPC64_ADDR16_HA: + if (apply_addr16_ha (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_ADDR16_HI: + if (apply_addr16_hi (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_ADDR16_LO: + if (apply_addr16_lo (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_ADDR16_LO_DS: + if (apply_addr16_lo_ds (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_ADDR32: + if (apply_addr32 (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_ADDR64: + v = ppc64_adjust_symval (hfile, isym, v); + *((Elf64_Xword *) loc) = v; + break; + + case R_PPC64_TOC: + *((Elf64_Xword *) loc) = hfile->my_tocbase; + break; + + case R_PPC64_TOC16_DS: + v -= hfile->my_tocbase; + if (apply_toc16_ds (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_TOC16: + v -= hfile->my_tocbase; + if (apply_toc16 (loc, v)) + ret = obj_reloc_overflow; + break; + + case R_PPC64_REL24: + assert(isym != NULL); + if (DEBUG_PPC > 1) + fprintf (stderr, "ppc_rel24 %s got=%p addend=%llx\n", + isym->root.name, isym->got_ent, + (unsigned long long) rel->r_addend); + for (ge = isym->got_ent; ge != NULL; ge = ge->next) + { + if (DEBUG_PPC > 1) + fprintf (stderr, "ge = %p addend=%llx\n", + ge, (unsigned long long) ge->addend); + if (ge->addend == rel->r_addend) + break; + } + + if (ge) + { + int stub_offset = ge->offset / 8 * SIZEOF_STUB; + if (!ge->reloc_done) + { + char *stub_loc; + Elf64_Addr *opd_ptr; + Elf64_Addr toc_offset; + + ge->reloc_done = 1; + + /* Set up the toc entry that points at the function opd. */ + v = isym->opd_sym->root.value; + v = ppc64_adjust_symval (hfile, isym->opd_sym, v); + opd_ptr = (Elf64_Addr *) (hfile->got + ge->offset); + *opd_ptr = v; + + /* Copy our call stub. */ + stub_loc = hfile->stub->contents + stub_offset; + if (DEBUG_PPC > 1) + fprintf (stderr, "stub: %p\n", stub_loc); + memcpy (stub_loc, ppc64_stub, SIZEOF_STUB); + + /* And patch in the correct offset. */ + toc_offset = (char *) opd_ptr - hfile->toc->contents - 0x8000; + if (apply_toc16_ds (stub_loc + 2, toc_offset)) + ret = obj_reloc_overflow; + } + + /* Point the branch insn at the stub. */ + v = hfile->stub->header.sh_addr + stub_offset; + } + else if (DEBUG_PPC > 1) + fprintf (stderr, "hi im here %s\n", isym->root.name); + + /* Relocate branch insn. */ + dot = targsec->header.sh_addr + rel->r_offset; + v -= dot; + if (v + 0x2000000 > 0x3ffffff || (v & 3) != 0) + { + if (DEBUG_PPC > 0) + fprintf (stderr, "rel24 overflow\n"); + ret = obj_reloc_overflow; + } + *((Elf64_Word *) loc) = ((*((Elf64_Word *) loc) & ~0x3fffffc) + | (v & 0x3fffffc)); + + if (ge) + { + Elf64_Word next_insn = 0; + + /* If the next insn is a nop (ori r0,r0,0 or cror 15,15,15 + or cror 31,31,31), patch it to restore the module r2. */ + if (rel->r_offset + 7 < targsec->header.sh_size + && ((next_insn = *((Elf64_Word *) loc + 1)) == 0x60000000 + || next_insn == 0x4def7b82 + || next_insn == 0x4ffffb82)) + { + *((Elf64_Word *) loc + 1) = 0xe8410028; /* ld r2,40(r1) */ + } + else if (ret == obj_reloc_ok + && next_insn != 0xe8410028) + { + ret = obj_reloc_dangerous; + } + } + break; + + case R_PPC64_REL14: + dot = targsec->header.sh_addr + rel->r_offset; + v -= dot; + if (v + 0x8000 > 0xffff || (v & 3) != 0) + { + if (DEBUG_PPC > 0) + fprintf (stderr, "rel14 overflow\n"); + ret = obj_reloc_overflow; + } + *((Elf64_Word *) loc) = ((*((Elf64_Word *) loc) & ~0xfffc) + | (v & 0xfffc)); + break; + + case R_PPC64_NONE: + break; + } + + return ret; +} + +int +arch_init_module (struct obj_file *f, struct module *mod) +{ + return 1; +} + +int +arch_archdata (struct obj_file *f, struct obj_section *archdata_sec) +{ + return 0; +} -- cgit v1.2.1