diff options
author | Stefan Reinauer <reinauer@chromium.org> | 2012-11-30 12:23:45 -0800 |
---|---|---|
committer | Stefan Reinauer <stefan.reinauer@coreboot.org> | 2012-11-30 21:56:33 +0100 |
commit | 1e753294c4c7bbc825a413ab62c47878104f984f (patch) | |
tree | b6096874eed22aa72c28f7a3bdf057b16d513e8a /src/lib | |
parent | 179206a1acb1659ffe018378494abc269771e9dd (diff) | |
download | coreboot-1e753294c4c7bbc825a413ab62c47878104f984f.tar.xz |
Drop boot directory
It only has two files, move them to src/lib
Change-Id: I17943db4c455aa3a934db1cf56e56e89c009679f
Signed-off-by: Stefan Reinauer <reinauer@google.com>
Reviewed-on: http://review.coreboot.org/1959
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Tested-by: build bot (Jenkins)
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/Makefile.inc | 2 | ||||
-rw-r--r-- | src/lib/hardwaremain.c | 147 | ||||
-rw-r--r-- | src/lib/selfboot.c | 543 |
3 files changed, 692 insertions, 0 deletions
diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index f99ca1b002..3b161705ed 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -39,6 +39,8 @@ romstage-$(CONFIG_COLLECT_TIMESTAMPS) += timestamp.c romstage-y += compute_ip_checksum.c romstage-y += memmove.c +ramstage-y += hardwaremain.c +ramstage-y += selfboot.c ifneq ($(CONFIG_HAVE_ARCH_MEMSET),y) ramstage-y += memset.c endif diff --git a/src/lib/hardwaremain.c b/src/lib/hardwaremain.c new file mode 100644 index 0000000000..206e82b066 --- /dev/null +++ b/src/lib/hardwaremain.c @@ -0,0 +1,147 @@ +/* +This software and ancillary information (herein called SOFTWARE ) +called LinuxBIOS is made available under the terms described +here. The SOFTWARE has been approved for release with associated +LA-CC Number 00-34 . Unless otherwise indicated, this SOFTWARE has +been authored by an employee or employees of the University of +California, operator of the Los Alamos National Laboratory under +Contract No. W-7405-ENG-36 with the U.S. Department of Energy. The +U.S. Government has rights to use, reproduce, and distribute this +SOFTWARE. The public may copy, distribute, prepare derivative works +and publicly display this SOFTWARE without charge, provided that this +Notice and any statement of authorship are reproduced on all copies. +Neither the Government nor the University makes any warranty, express +or implied, or assumes any liability or responsibility for the use of +this SOFTWARE. If SOFTWARE is modified to produce derivative works, +such modified SOFTWARE should be clearly marked, so as not to confuse +it with the version available from LANL. + */ +/* Copyright 2000, Ron Minnich, Advanced Computing Lab, LANL + * rminnich@lanl.gov + */ + + +/* + * C Bootstrap code for the coreboot + */ + +#include <console/console.h> +#include <version.h> +#include <device/device.h> +#include <device/pci.h> +#include <delay.h> +#include <stdlib.h> +#include <reset.h> +#include <boot/tables.h> +#include <boot/elf.h> +#include <cbfs.h> +#include <lib.h> +#if CONFIG_HAVE_ACPI_RESUME +#include <arch/acpi.h> +#endif +#if CONFIG_WRITE_HIGH_TABLES +#include <cbmem.h> +#endif +#include <timestamp.h> + +/** + * @brief Main function of the RAM part of coreboot. + * + * Coreboot is divided into Pre-RAM part and RAM part. + * + * Device Enumeration: + * In the dev_enumerate() phase, + */ + +void hardwaremain(int boot_complete); + +void hardwaremain(int boot_complete) +{ + struct lb_memory *lb_mem; + + timestamp_stash(TS_START_RAMSTAGE); + post_code(POST_ENTRY_RAMSTAGE); + + /* console_init() MUST PRECEDE ALL printk()! */ + console_init(); + + post_code(POST_CONSOLE_READY); + + printk(BIOS_NOTICE, "coreboot-%s%s %s %s...\n", + coreboot_version, coreboot_extra_version, coreboot_build, + (boot_complete)?"rebooting":"booting"); + + post_code(POST_CONSOLE_BOOT_MSG); + + /* If we have already booted attempt a hard reboot */ + if (boot_complete) { + hard_reset(); + } + + /* FIXME: Is there a better way to handle this? */ + init_timer(); + + timestamp_stash(TS_DEVICE_ENUMERATE); + + /* Initialize chips early, they might disable unused devices. */ + dev_initialize_chips(); + + /* Find the devices we don't have hard coded knowledge about. */ + dev_enumerate(); + post_code(POST_DEVICE_ENUMERATION_COMPLETE); + + timestamp_stash(TS_DEVICE_CONFIGURE); + /* Now compute and assign the bus resources. */ + dev_configure(); + post_code(POST_DEVICE_CONFIGURATION_COMPLETE); + + timestamp_stash(TS_DEVICE_ENABLE); + /* Now actually enable devices on the bus */ + dev_enable(); + post_code(POST_DEVICES_ENABLED); + + timestamp_stash(TS_DEVICE_INITIALIZE); + /* And of course initialize devices on the bus */ + dev_initialize(); + post_code(POST_DEVICES_INITIALIZED); + + timestamp_stash(TS_DEVICE_DONE); + +#if CONFIG_WRITE_HIGH_TABLES + cbmem_initialize(); +#if CONFIG_CONSOLE_CBMEM + cbmemc_reinit(); +#endif +#endif + timestamp_sync(); + +#if CONFIG_HAVE_ACPI_RESUME + suspend_resume(); + post_code(0x8a); +#endif + + timestamp_add_now(TS_CBMEM_POST); + +#if CONFIG_WRITE_HIGH_TABLES + if (cbmem_post_handling) + cbmem_post_handling(); +#endif + + timestamp_add_now(TS_WRITE_TABLES); + + /* Now that we have collected all of our information + * write our configuration tables. + */ + lb_mem = write_tables(); + + timestamp_add_now(TS_LOAD_PAYLOAD); + + void *payload; + payload = cbfs_load_payload(lb_mem, CONFIG_CBFS_PREFIX "/payload"); + if (! payload) + die("Could not find a payload\n"); + + selfboot(lb_mem, payload); + printk(BIOS_EMERG, "Boot failed"); +} + diff --git a/src/lib/selfboot.c b/src/lib/selfboot.c new file mode 100644 index 0000000000..2556a14d6e --- /dev/null +++ b/src/lib/selfboot.c @@ -0,0 +1,543 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2003 Eric W. Biederman <ebiederm@xmission.com> + * Copyright (C) 2009 Ron Minnich <rminnich@gmail.com> + * + * 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; version 2 of the License. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA + */ + +#include <arch/byteorder.h> +#include <console/console.h> +#include <fallback.h> +#include <boot/elf.h> +#include <boot/elf_boot.h> +#include <boot/coreboot_tables.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <cbfs.h> +#include <lib.h> +#if CONFIG_COLLECT_TIMESTAMPS +#include <timestamp.h> +#endif + +/* Maximum physical address we can use for the coreboot bounce buffer. */ +#ifndef MAX_ADDR +#define MAX_ADDR -1UL +#endif + +/* from coreboot_ram.ld: */ +extern unsigned char _ram_seg; +extern unsigned char _eram_seg; + +static const unsigned long lb_start = (unsigned long)&_ram_seg; +static const unsigned long lb_end = (unsigned long)&_eram_seg; + +struct segment { + struct segment *next; + struct segment *prev; + unsigned long s_dstaddr; + unsigned long s_srcaddr; + unsigned long s_memsz; + unsigned long s_filesz; + int compression; +}; + +/* The problem: + * Static executables all want to share the same addresses + * in memory because only a few addresses are reliably present on + * a machine, and implementing general relocation is hard. + * + * The solution: + * - Allocate a buffer the size of the coreboot image plus additional + * required space. + * - Anything that would overwrite coreboot copy into the lower part of + * the buffer. + * - After loading an ELF image copy coreboot to the top of the buffer. + * - Then jump to the loaded image. + * + * Benefits: + * - Nearly arbitrary standalone executables can be loaded. + * - Coreboot is preserved, so it can be returned to. + * - The implementation is still relatively simple, + * and much simpler than the general case implemented in kexec. + */ + +static unsigned long bounce_size, bounce_buffer; + +static void get_bounce_buffer(struct lb_memory *mem, unsigned long req_size) +{ + unsigned long lb_size; + unsigned long mem_entries; + unsigned long buffer; + int i; + lb_size = lb_end - lb_start; + /* Plus coreboot size so I have somewhere + * to place a copy to return to. + */ + lb_size = req_size + lb_size; + mem_entries = (mem->size - sizeof(*mem)) / sizeof(mem->map[0]); + buffer = 0; + for(i = 0; i < mem_entries; i++) { + unsigned long mstart, mend; + unsigned long msize; + unsigned long tbuffer; + if (mem->map[i].type != LB_MEM_RAM) + continue; + if (unpack_lb64(mem->map[i].start) > MAX_ADDR) + continue; + if (unpack_lb64(mem->map[i].size) < lb_size) + continue; + mstart = unpack_lb64(mem->map[i].start); + msize = MAX_ADDR - mstart +1; + if (msize > unpack_lb64(mem->map[i].size)) + msize = unpack_lb64(mem->map[i].size); + mend = mstart + msize; + tbuffer = mend - lb_size; + if (tbuffer < buffer) + continue; + buffer = tbuffer; + } + bounce_buffer = buffer; + bounce_size = req_size; +} + +static int valid_area(struct lb_memory *mem, unsigned long buffer, + unsigned long start, unsigned long len) +{ + /* Check through all of the memory segments and ensure + * the segment that was passed in is completely contained + * in RAM. + */ + int i; + unsigned long end = start + len; + unsigned long mem_entries = (mem->size - sizeof(*mem)) / + sizeof(mem->map[0]); + + /* See if I conflict with the bounce buffer */ + if (end >= buffer) { + return 0; + } + + /* Walk through the table of valid memory ranges and see if I + * have a match. + */ + for(i = 0; i < mem_entries; i++) { + uint64_t mstart, mend; + uint32_t mtype; + mtype = mem->map[i].type; + mstart = unpack_lb64(mem->map[i].start); + mend = mstart + unpack_lb64(mem->map[i].size); + if ((mtype == LB_MEM_RAM) && (start >= mstart) && (end < mend)) { + break; + } + if ((mtype == LB_MEM_TABLE) && (start >= mstart) && (end < mend)) { + printk(BIOS_ERR, "Payload is overwriting coreboot tables.\n"); + break; + } + } + if (i == mem_entries) { + if (start < (1024*1024) && end <=(1024*1024)) { + printk(BIOS_DEBUG, "Payload (probably SeaBIOS) loaded" + " into a reserved area in the lower 1MB\n"); + return 1; + } + printk(BIOS_ERR, "No matching ram area found for range:\n"); + printk(BIOS_ERR, " [0x%016lx, 0x%016lx)\n", start, end); + printk(BIOS_ERR, "Ram areas\n"); + for(i = 0; i < mem_entries; i++) { + uint64_t mstart, mend; + uint32_t mtype; + mtype = mem->map[i].type; + mstart = unpack_lb64(mem->map[i].start); + mend = mstart + unpack_lb64(mem->map[i].size); + printk(BIOS_ERR, " [0x%016lx, 0x%016lx) %s\n", + (unsigned long)mstart, + (unsigned long)mend, + (mtype == LB_MEM_RAM)?"RAM":"Reserved"); + + } + return 0; + } + return 1; +} + + +static int overlaps_coreboot(struct segment *seg) +{ + unsigned long start, end; + start = seg->s_dstaddr; + end = start + seg->s_memsz; + return !((end <= lb_start) || (start >= lb_end)); +} + +static int relocate_segment(unsigned long buffer, struct segment *seg) +{ + /* Modify all segments that want to load onto coreboot + * to load onto the bounce buffer instead. + */ + /* ret: 1 : A new segment is inserted before the seg. + * 0 : A new segment is inserted after the seg, or no new one. + */ + unsigned long start, middle, end, ret = 0; + + printk(BIOS_SPEW, "lb: [0x%016lx, 0x%016lx)\n", + lb_start, lb_end); + + /* I don't conflict with coreboot so get out of here */ + if (!overlaps_coreboot(seg)) + return 0; + + start = seg->s_dstaddr; + middle = start + seg->s_filesz; + end = start + seg->s_memsz; + + printk(BIOS_SPEW, "segment: [0x%016lx, 0x%016lx, 0x%016lx)\n", + start, middle, end); + + if (seg->compression == CBFS_COMPRESS_NONE) { + /* Slice off a piece at the beginning + * that doesn't conflict with coreboot. + */ + if (start < lb_start) { + struct segment *new; + unsigned long len = lb_start - start; + new = malloc(sizeof(*new)); + *new = *seg; + new->s_memsz = len; + seg->s_memsz -= len; + seg->s_dstaddr += len; + seg->s_srcaddr += len; + if (seg->s_filesz > len) { + new->s_filesz = len; + seg->s_filesz -= len; + } else { + seg->s_filesz = 0; + } + + /* Order by stream offset */ + new->next = seg; + new->prev = seg->prev; + seg->prev->next = new; + seg->prev = new; + + /* compute the new value of start */ + start = seg->s_dstaddr; + + printk(BIOS_SPEW, " early: [0x%016lx, 0x%016lx, 0x%016lx)\n", + new->s_dstaddr, + new->s_dstaddr + new->s_filesz, + new->s_dstaddr + new->s_memsz); + + ret = 1; + } + + /* Slice off a piece at the end + * that doesn't conflict with coreboot + */ + if (end > lb_end) { + unsigned long len = lb_end - start; + struct segment *new; + new = malloc(sizeof(*new)); + *new = *seg; + seg->s_memsz = len; + new->s_memsz -= len; + new->s_dstaddr += len; + new->s_srcaddr += len; + if (seg->s_filesz > len) { + seg->s_filesz = len; + new->s_filesz -= len; + } else { + new->s_filesz = 0; + } + /* Order by stream offset */ + new->next = seg->next; + new->prev = seg; + seg->next->prev = new; + seg->next = new; + + printk(BIOS_SPEW, " late: [0x%016lx, 0x%016lx, 0x%016lx)\n", + new->s_dstaddr, + new->s_dstaddr + new->s_filesz, + new->s_dstaddr + new->s_memsz); + } + } + + /* Now retarget this segment onto the bounce buffer */ + /* sort of explanation: the buffer is a 1:1 mapping to coreboot. + * so you will make the dstaddr be this buffer, and it will get copied + * later to where coreboot lives. + */ + seg->s_dstaddr = buffer + (seg->s_dstaddr - lb_start); + + printk(BIOS_SPEW, " bounce: [0x%016lx, 0x%016lx, 0x%016lx)\n", + seg->s_dstaddr, + seg->s_dstaddr + seg->s_filesz, + seg->s_dstaddr + seg->s_memsz); + + return ret; +} + + +static int build_self_segment_list( + struct segment *head, + struct lb_memory *mem, + struct cbfs_payload *payload, u32 *entry) +{ + struct segment *new; + struct segment *ptr; + struct cbfs_payload_segment *segment, *first_segment; + memset(head, 0, sizeof(*head)); + head->next = head->prev = head; + first_segment = segment = &payload->segments; + + while(1) { + printk(BIOS_DEBUG, "Loading segment from rom address 0x%p\n", segment); + switch(segment->type) { + case PAYLOAD_SEGMENT_PARAMS: + printk(BIOS_DEBUG, " parameter section (skipped)\n"); + segment++; + continue; + + case PAYLOAD_SEGMENT_CODE: + case PAYLOAD_SEGMENT_DATA: + printk(BIOS_DEBUG, " %s (compression=%x)\n", + segment->type == PAYLOAD_SEGMENT_CODE ? "code" : "data", + ntohl(segment->compression)); + new = malloc(sizeof(*new)); + new->s_dstaddr = ntohll(segment->load_addr); + new->s_memsz = ntohl(segment->mem_len); + new->compression = ntohl(segment->compression); + + new->s_srcaddr = (u32) ((unsigned char *)first_segment) + + ntohl(segment->offset); + new->s_filesz = ntohl(segment->len); + printk(BIOS_DEBUG, " New segment dstaddr 0x%lx memsize 0x%lx srcaddr 0x%lx filesize 0x%lx\n", + new->s_dstaddr, new->s_memsz, new->s_srcaddr, new->s_filesz); + /* Clean up the values */ + if (new->s_filesz > new->s_memsz) { + new->s_filesz = new->s_memsz; + } + printk(BIOS_DEBUG, " (cleaned up) New segment addr 0x%lx size 0x%lx offset 0x%lx filesize 0x%lx\n", + new->s_dstaddr, new->s_memsz, new->s_srcaddr, new->s_filesz); + break; + + case PAYLOAD_SEGMENT_BSS: + printk(BIOS_DEBUG, " BSS 0x%p (%d byte)\n", (void *) + (intptr_t)ntohll(segment->load_addr), + ntohl(segment->mem_len)); + new = malloc(sizeof(*new)); + new->s_filesz = 0; + new->s_dstaddr = ntohll(segment->load_addr); + new->s_memsz = ntohl(segment->mem_len); + break; + + case PAYLOAD_SEGMENT_ENTRY: + printk(BIOS_DEBUG, " Entry Point 0x%p\n", (void *) ntohl((u32) segment->load_addr)); + *entry = ntohll(segment->load_addr); + /* Per definition, a payload always has the entry point + * as last segment. Thus, we use the occurence of the + * entry point as break condition for the loop. + * Can we actually just look at the number of section? + */ + return 1; + + default: + /* We found something that we don't know about. Throw + * hands into the sky and run away! + */ + printk(BIOS_EMERG, "Bad segment type %x\n", segment->type); + return -1; + } + + /* We have found another CODE, DATA or BSS segment */ + segment++; + + /* Find place where to insert our segment */ + for(ptr = head->next; ptr != head; ptr = ptr->next) { + if (new->s_srcaddr < ntohll(segment->load_addr)) + break; + } + + /* Order by stream offset */ + new->next = ptr; + new->prev = ptr->prev; + ptr->prev->next = new; + ptr->prev = new; + } + + return 1; +} + +static int load_self_segments( + struct segment *head, + struct lb_memory *mem, + struct cbfs_payload *payload) +{ + struct segment *ptr; + + unsigned long bounce_high = lb_end; + for(ptr = head->next; ptr != head; ptr = ptr->next) { + if (!overlaps_coreboot(ptr)) + continue; + if (ptr->s_dstaddr + ptr->s_memsz > bounce_high) + bounce_high = ptr->s_dstaddr + ptr->s_memsz; + } + get_bounce_buffer(mem, bounce_high - lb_start); + if (!bounce_buffer) { + printk(BIOS_ERR, "Could not find a bounce buffer...\n"); + return 0; + } + for(ptr = head->next; ptr != head; ptr = ptr->next) { + /* Verify the memory addresses in the segment are valid */ + if (!valid_area(mem, bounce_buffer, ptr->s_dstaddr, ptr->s_memsz)) + return 0; + } + for(ptr = head->next; ptr != head; ptr = ptr->next) { + unsigned char *dest, *src; + printk(BIOS_DEBUG, "Loading Segment: addr: 0x%016lx memsz: 0x%016lx filesz: 0x%016lx\n", + ptr->s_dstaddr, ptr->s_memsz, ptr->s_filesz); + + /* Modify the segment to load onto the bounce_buffer if necessary. + */ + if (relocate_segment(bounce_buffer, ptr)) { + ptr = (ptr->prev)->prev; + continue; + } + + printk(BIOS_DEBUG, "Post relocation: addr: 0x%016lx memsz: 0x%016lx filesz: 0x%016lx\n", + ptr->s_dstaddr, ptr->s_memsz, ptr->s_filesz); + + /* Compute the boundaries of the segment */ + dest = (unsigned char *)(ptr->s_dstaddr); + src = (unsigned char *)(ptr->s_srcaddr); + + /* Copy data from the initial buffer */ + if (ptr->s_filesz) { + unsigned char *middle, *end; + size_t len; + len = ptr->s_filesz; + switch(ptr->compression) { + case CBFS_COMPRESS_LZMA: { + printk(BIOS_DEBUG, "using LZMA\n"); + len = ulzma(src, dest); + if (!len) /* Decompression Error. */ + return 0; + break; + } +#if CONFIG_COMPRESSED_PAYLOAD_NRV2B + case CBFS_COMPRESS_NRV2B: { + printk(BIOS_DEBUG, "using NRV2B\n"); + unsigned long unrv2b(u8 *src, u8 *dst, unsigned long *ilen_p); + unsigned long tmp; + len = unrv2b(src, dest, &tmp); + break; + } +#endif + case CBFS_COMPRESS_NONE: { + printk(BIOS_DEBUG, "it's not compressed!\n"); + memcpy(dest, src, len); + break; + } + default: + printk(BIOS_INFO, "CBFS: Unknown compression type %d\n", ptr->compression); + return -1; + } + end = dest + ptr->s_memsz; + middle = dest + len; + printk(BIOS_SPEW, "[ 0x%08lx, %08lx, 0x%08lx) <- %08lx\n", + (unsigned long)dest, + (unsigned long)middle, + (unsigned long)end, + (unsigned long)src); + + /* Zero the extra bytes between middle & end */ + if (middle < end) { + printk(BIOS_DEBUG, "Clearing Segment: addr: 0x%016lx memsz: 0x%016lx\n", + (unsigned long)middle, (unsigned long)(end - middle)); + + /* Zero the extra bytes */ + memset(middle, 0, end - middle); + } + /* Copy the data that's outside the area that shadows coreboot_ram */ + printk(BIOS_DEBUG, "dest %p, end %p, bouncebuffer %lx\n", dest, end, bounce_buffer); + if ((unsigned long)end > bounce_buffer) { + if ((unsigned long)dest < bounce_buffer) { + unsigned char *from = dest; + unsigned char *to = (unsigned char*)(lb_start-(bounce_buffer-(unsigned long)dest)); + unsigned long amount = bounce_buffer-(unsigned long)dest; + printk(BIOS_DEBUG, "move prefix around: from %p, to %p, amount: %lx\n", from, to, amount); + memcpy(to, from, amount); + } + if ((unsigned long)end > bounce_buffer + (lb_end - lb_start)) { + unsigned long from = bounce_buffer + (lb_end - lb_start); + unsigned long to = lb_end; + unsigned long amount = (unsigned long)end - from; + printk(BIOS_DEBUG, "move suffix around: from %lx, to %lx, amount: %lx\n", from, to, amount); + memcpy((char*)to, (char*)from, amount); + } + } + } + } + return 1; +} + +int selfboot(struct lb_memory *mem, struct cbfs_payload *payload) +{ + u32 entry=0; + struct segment head; + + /* Preprocess the self segments */ + if (!build_self_segment_list(&head, mem, payload, &entry)) + goto out; + + /* Load the segments */ + if (!load_self_segments(&head, mem, payload)) + goto out; + + printk(BIOS_SPEW, "Loaded segments\n"); + + /* Reset to booting from this image as late as possible */ + boot_successful(); + + printk(BIOS_DEBUG, "Jumping to boot code at %x\n", entry); + post_code(POST_ENTER_ELF_BOOT); + +#if CONFIG_COLLECT_TIMESTAMPS + timestamp_add_now(TS_SELFBOOT_JUMP); +#endif + + /* Before we go off to run the payload, see if + * we stayed within our bounds. + */ + checkstack(_estack, 0); + + /* Jump to kernel */ + jmp_to_elf_entry((void*)entry, bounce_buffer, bounce_size); + return 1; + +out: + return 0; +} + +void *cbfs_load_payload(struct lb_memory *lb_mem, const char *name) +{ + struct cbfs_payload *payload; + + payload = (struct cbfs_payload *)cbfs_find_file(name, CBFS_TYPE_PAYLOAD); + + return payload; +} + |