2012-07-15 12:25:27 +04:00
/*
* Remote Processor Framework Elf loader
*
* Copyright ( C ) 2011 Texas Instruments , Inc .
* Copyright ( C ) 2011 Google , Inc .
*
* Ohad Ben - Cohen < ohad @ wizery . com >
* Brian Swetland < swetland @ google . com >
* Mark Grosen < mgrosen @ ti . com >
* Fernando Guzman Lugo < fernando . lugo @ ti . com >
* Suman Anna < s - anna @ ti . com >
* Robert Tivy < rtivy @ ti . com >
* Armando Uribe De Leon < x0095078 @ ti . com >
* Sjur Brændeland < sjur . brandeland @ stericsson . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation .
*
* 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 .
*/
# define pr_fmt(fmt) "%s: " fmt, __func__
# include <linux/module.h>
# include <linux/firmware.h>
# include <linux/remoteproc.h>
# include <linux/elf.h>
# include "remoteproc_internal.h"
/**
2012-06-19 11:08:18 +04:00
* rproc_elf_sanity_check ( ) - Sanity Check ELF firmware image
2012-07-15 12:25:27 +04:00
* @ rproc : the remote processor handle
* @ fw : the ELF firmware image
*
* Make sure this fw image is sane .
*/
2012-06-19 11:08:18 +04:00
static int
rproc_elf_sanity_check ( struct rproc * rproc , const struct firmware * fw )
2012-07-15 12:25:27 +04:00
{
const char * name = rproc - > firmware ;
struct device * dev = & rproc - > dev ;
struct elf32_hdr * ehdr ;
char class ;
if ( ! fw ) {
dev_err ( dev , " failed to load %s \n " , name ) ;
return - EINVAL ;
}
if ( fw - > size < sizeof ( struct elf32_hdr ) ) {
dev_err ( dev , " Image is too small \n " ) ;
return - EINVAL ;
}
ehdr = ( struct elf32_hdr * ) fw - > data ;
/* We only support ELF32 at this point */
class = ehdr - > e_ident [ EI_CLASS ] ;
if ( class ! = ELFCLASS32 ) {
dev_err ( dev , " Unsupported class: %d \n " , class ) ;
return - EINVAL ;
}
2012-11-30 09:10:25 +04:00
/* We assume the firmware has the same endianness as the host */
2012-07-15 12:25:27 +04:00
# ifdef __LITTLE_ENDIAN
if ( ehdr - > e_ident [ EI_DATA ] ! = ELFDATA2LSB ) {
# else /* BIG ENDIAN */
if ( ehdr - > e_ident [ EI_DATA ] ! = ELFDATA2MSB ) {
# endif
2012-11-30 09:10:25 +04:00
dev_err ( dev , " Unsupported firmware endianness \n " ) ;
2012-07-15 12:25:27 +04:00
return - EINVAL ;
}
if ( fw - > size < ehdr - > e_shoff + sizeof ( struct elf32_shdr ) ) {
dev_err ( dev , " Image is too small \n " ) ;
return - EINVAL ;
}
if ( memcmp ( ehdr - > e_ident , ELFMAG , SELFMAG ) ) {
dev_err ( dev , " Image is corrupted (bad magic) \n " ) ;
return - EINVAL ;
}
if ( ehdr - > e_phnum = = 0 ) {
dev_err ( dev , " No loadable segments \n " ) ;
return - EINVAL ;
}
if ( ehdr - > e_phoff > fw - > size ) {
dev_err ( dev , " Firmware size is too small \n " ) ;
return - EINVAL ;
}
return 0 ;
}
/**
2012-06-19 11:08:18 +04:00
* rproc_elf_get_boot_addr ( ) - Get rproc ' s boot address .
2012-07-15 12:25:27 +04:00
* @ rproc : the remote processor handle
* @ fw : the ELF firmware image
*
* This function returns the entry point address of the ELF
* image .
*
* Note that the boot address is not a configurable property of all remote
* processors . Some will always boot at a specific hard - coded address .
*/
2012-06-19 11:08:18 +04:00
static
u32 rproc_elf_get_boot_addr ( struct rproc * rproc , const struct firmware * fw )
2012-07-15 12:25:27 +04:00
{
struct elf32_hdr * ehdr = ( struct elf32_hdr * ) fw - > data ;
return ehdr - > e_entry ;
}
/**
2012-06-19 11:08:18 +04:00
* rproc_elf_load_segments ( ) - load firmware segments to memory
2012-07-15 12:25:27 +04:00
* @ rproc : remote processor which will be booted using these fw segments
* @ fw : the ELF firmware image
*
* This function loads the firmware segments to memory , where the remote
* processor expects them .
*
* Some remote processors will expect their code and data to be placed
* in specific device addresses , and can ' t have them dynamically assigned .
*
* We currently support only those kind of remote processors , and expect
* the program header ' s paddr member to contain those addresses . We then go
* through the physically contiguous " carveout " memory regions which we
* allocated ( and mapped ) earlier on behalf of the remote processor ,
* and " translate " device address to kernel addresses , so we can copy the
* segments where they are expected .
*
* Currently we only support remote processors that required carveout
* allocations and got them mapped onto their iommus . Some processors
* might be different : they might not have iommus , and would prefer to
* directly allocate memory for every segment / resource . This is not yet
* supported , though .
*/
2012-06-19 11:08:18 +04:00
static int
rproc_elf_load_segments ( struct rproc * rproc , const struct firmware * fw )
2012-07-15 12:25:27 +04:00
{
struct device * dev = & rproc - > dev ;
struct elf32_hdr * ehdr ;
struct elf32_phdr * phdr ;
int i , ret = 0 ;
const u8 * elf_data = fw - > data ;
ehdr = ( struct elf32_hdr * ) elf_data ;
phdr = ( struct elf32_phdr * ) ( elf_data + ehdr - > e_phoff ) ;
/* go through the available ELF segments */
for ( i = 0 ; i < ehdr - > e_phnum ; i + + , phdr + + ) {
u32 da = phdr - > p_paddr ;
u32 memsz = phdr - > p_memsz ;
u32 filesz = phdr - > p_filesz ;
u32 offset = phdr - > p_offset ;
void * ptr ;
if ( phdr - > p_type ! = PT_LOAD )
continue ;
dev_dbg ( dev , " phdr: type %d da 0x%x memsz 0x%x filesz 0x%x \n " ,
phdr - > p_type , da , memsz , filesz ) ;
if ( filesz > memsz ) {
dev_err ( dev , " bad phdr filesz 0x%x memsz 0x%x \n " ,
filesz , memsz ) ;
ret = - EINVAL ;
break ;
}
if ( offset + filesz > fw - > size ) {
2012-07-27 03:19:08 +04:00
dev_err ( dev , " truncated fw: need 0x%x avail 0x%zx \n " ,
2012-07-15 12:25:27 +04:00
offset + filesz , fw - > size ) ;
ret = - EINVAL ;
break ;
}
/* grab the kernel address for this device address */
ptr = rproc_da_to_va ( rproc , da , memsz ) ;
if ( ! ptr ) {
dev_err ( dev , " bad phdr da 0x%x mem 0x%x \n " , da , memsz ) ;
ret = - EINVAL ;
break ;
}
/* put the segment where the remote processor expects it */
if ( phdr - > p_filesz )
memcpy ( ptr , elf_data + phdr - > p_offset , filesz ) ;
/*
* Zero out remaining memory for this segment .
*
* This isn ' t strictly required since dma_alloc_coherent already
* did this for us . albeit harmless , we may consider removing
* this .
*/
if ( memsz > filesz )
memset ( ptr + filesz , 0 , memsz - filesz ) ;
}
return ret ;
}
/**
2012-06-19 11:08:18 +04:00
* rproc_elf_find_rsc_table ( ) - find the resource table
2012-07-15 12:25:27 +04:00
* @ rproc : the rproc handle
* @ fw : the ELF firmware image
* @ tablesz : place holder for providing back the table size
*
* This function finds the resource table inside the remote processor ' s
* firmware . It is used both upon the registration of @ rproc ( in order
* to look for and register the supported virito devices ) , and when the
* @ rproc is booted .
*
* Returns the pointer to the resource table if it is found , and write its
* size into @ tablesz . If a valid table isn ' t found , NULL is returned
* ( and @ tablesz isn ' t set ) .
*/
2012-06-19 11:08:18 +04:00
static struct resource_table *
rproc_elf_find_rsc_table ( struct rproc * rproc , const struct firmware * fw ,
2012-07-15 12:25:27 +04:00
int * tablesz )
{
struct elf32_hdr * ehdr ;
struct elf32_shdr * shdr ;
const char * name_table ;
struct device * dev = & rproc - > dev ;
struct resource_table * table = NULL ;
int i ;
const u8 * elf_data = fw - > data ;
ehdr = ( struct elf32_hdr * ) elf_data ;
shdr = ( struct elf32_shdr * ) ( elf_data + ehdr - > e_shoff ) ;
name_table = elf_data + shdr [ ehdr - > e_shstrndx ] . sh_offset ;
/* look for the resource table and handle it */
for ( i = 0 ; i < ehdr - > e_shnum ; i + + , shdr + + ) {
int size = shdr - > sh_size ;
int offset = shdr - > sh_offset ;
if ( strcmp ( name_table + shdr - > sh_name , " .resource_table " ) )
continue ;
table = ( struct resource_table * ) ( elf_data + offset ) ;
/* make sure we have the entire table */
if ( offset + size > fw - > size ) {
dev_err ( dev , " resource table truncated \n " ) ;
return NULL ;
}
/* make sure table has at least the header */
if ( sizeof ( struct resource_table ) > size ) {
dev_err ( dev , " header-less resource table \n " ) ;
return NULL ;
}
/* we don't support any version beyond the first */
if ( table - > ver ! = 1 ) {
dev_err ( dev , " unsupported fw ver: %d \n " , table - > ver ) ;
return NULL ;
}
/* make sure reserved bytes are zeroes */
if ( table - > reserved [ 0 ] | | table - > reserved [ 1 ] ) {
dev_err ( dev , " non zero reserved bytes \n " ) ;
return NULL ;
}
/* make sure the offsets array isn't truncated */
if ( table - > num * sizeof ( table - > offset [ 0 ] ) +
sizeof ( struct resource_table ) > size ) {
dev_err ( dev , " resource table incomplete \n " ) ;
return NULL ;
}
* tablesz = shdr - > sh_size ;
break ;
}
return table ;
}
2012-06-19 11:08:18 +04:00
const struct rproc_fw_ops rproc_elf_fw_ops = {
. load = rproc_elf_load_segments ,
. find_rsc_table = rproc_elf_find_rsc_table ,
. sanity_check = rproc_elf_sanity_check ,
. get_boot_addr = rproc_elf_get_boot_addr
} ;