2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2016-06-21 00:28:41 +03:00
/*
* Qualcomm Peripheral Image Loader
*
* Copyright ( C ) 2016 Linaro Ltd
* Copyright ( C ) 2015 Sony Mobile Communications Inc
* Copyright ( c ) 2012 - 2013 , The Linux Foundation . All rights reserved .
*/
2017-01-27 13:17:23 +03:00
# include <linux/device.h>
2016-06-21 00:28:41 +03:00
# include <linux/elf.h>
# include <linux/firmware.h>
# include <linux/kernel.h>
# include <linux/module.h>
2017-01-27 13:17:23 +03:00
# include <linux/qcom_scm.h>
2016-11-22 20:02:17 +03:00
# include <linux/sizes.h>
2016-06-21 00:28:41 +03:00
# include <linux/slab.h>
2017-01-27 14:12:57 +03:00
# include <linux/soc/qcom/mdt_loader.h>
2016-06-21 00:28:41 +03:00
2017-01-27 13:17:23 +03:00
static bool mdt_phdr_valid ( const struct elf32_phdr * phdr )
{
if ( phdr - > p_type ! = PT_LOAD )
return false ;
if ( ( phdr - > p_flags & QCOM_MDT_TYPE_MASK ) = = QCOM_MDT_TYPE_HASH )
return false ;
if ( ! phdr - > p_memsz )
return false ;
return true ;
}
2016-06-21 00:28:41 +03:00
/**
2017-01-27 13:17:23 +03:00
* qcom_mdt_get_size ( ) - acquire size of the memory region needed to load mdt
* @ fw : firmware object for the mdt file
2016-06-21 00:28:41 +03:00
*
2017-01-27 13:17:23 +03:00
* Returns size of the loaded firmware blob , or - EINVAL on failure .
2016-06-21 00:28:41 +03:00
*/
2017-01-27 13:17:23 +03:00
ssize_t qcom_mdt_get_size ( const struct firmware * fw )
2016-06-21 00:28:41 +03:00
{
const struct elf32_phdr * phdrs ;
const struct elf32_phdr * phdr ;
const struct elf32_hdr * ehdr ;
2018-06-15 01:28:02 +03:00
phys_addr_t min_addr = PHYS_ADDR_MAX ;
2016-06-21 00:28:41 +03:00
phys_addr_t max_addr = 0 ;
int i ;
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
for ( i = 0 ; i < ehdr - > e_phnum ; i + + ) {
phdr = & phdrs [ i ] ;
2017-01-27 13:17:23 +03:00
if ( ! mdt_phdr_valid ( phdr ) )
2016-06-21 00:28:41 +03:00
continue ;
if ( phdr - > p_paddr < min_addr )
min_addr = phdr - > p_paddr ;
if ( phdr - > p_paddr + phdr - > p_memsz > max_addr )
max_addr = ALIGN ( phdr - > p_paddr + phdr - > p_memsz , SZ_4K ) ;
}
2017-01-27 13:17:23 +03:00
return min_addr < max_addr ? max_addr - min_addr : - EINVAL ;
2016-06-21 00:28:41 +03:00
}
2017-01-27 13:17:23 +03:00
EXPORT_SYMBOL_GPL ( qcom_mdt_get_size ) ;
2016-06-21 00:28:41 +03:00
2019-06-22 04:21:45 +03:00
/**
* qcom_mdt_read_metadata ( ) - read header and metadata from mdt or mbn
* @ fw : firmware of mdt header or mbn
* @ data_len : length of the read metadata blob
*
* The mechanism that performs the authentication of the loading firmware
* expects an ELF header directly followed by the segment of hashes , with no
* padding inbetween . This function allocates a chunk of memory for this pair
* and copy the two pieces into the buffer .
*
* In the case of split firmware the hash is found directly following the ELF
* header , rather than at p_offset described by the second program header .
*
* The caller is responsible to free ( kfree ( ) ) the returned pointer .
*
* Return : pointer to data , or ERR_PTR ( )
*/
void * qcom_mdt_read_metadata ( const struct firmware * fw , size_t * data_len )
{
const struct elf32_phdr * phdrs ;
const struct elf32_hdr * ehdr ;
size_t hash_offset ;
size_t hash_size ;
size_t ehdr_size ;
void * data ;
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
if ( ehdr - > e_phnum < 2 )
return ERR_PTR ( - EINVAL ) ;
if ( phdrs [ 0 ] . p_type = = PT_LOAD | | phdrs [ 1 ] . p_type = = PT_LOAD )
return ERR_PTR ( - EINVAL ) ;
if ( ( phdrs [ 1 ] . p_flags & QCOM_MDT_TYPE_MASK ) ! = QCOM_MDT_TYPE_HASH )
return ERR_PTR ( - EINVAL ) ;
ehdr_size = phdrs [ 0 ] . p_filesz ;
hash_size = phdrs [ 1 ] . p_filesz ;
data = kmalloc ( ehdr_size + hash_size , GFP_KERNEL ) ;
if ( ! data )
return ERR_PTR ( - ENOMEM ) ;
/* Is the header and hash already packed */
if ( ehdr_size + hash_size = = fw - > size )
hash_offset = phdrs [ 0 ] . p_filesz ;
else
hash_offset = phdrs [ 1 ] . p_offset ;
memcpy ( data , fw - > data , ehdr_size ) ;
memcpy ( data + ehdr_size , fw - > data + hash_offset , hash_size ) ;
* data_len = ehdr_size + hash_size ;
return data ;
}
EXPORT_SYMBOL_GPL ( qcom_mdt_read_metadata ) ;
2018-06-04 23:30:35 +03:00
static int __qcom_mdt_load ( struct device * dev , const struct firmware * fw ,
const char * firmware , int pas_id , void * mem_region ,
phys_addr_t mem_phys , size_t mem_size ,
phys_addr_t * reloc_base , bool pas_init )
2016-06-21 00:28:41 +03:00
{
const struct elf32_phdr * phdrs ;
const struct elf32_phdr * phdr ;
const struct elf32_hdr * ehdr ;
2017-01-27 13:06:36 +03:00
const struct firmware * seg_fw ;
2017-01-27 13:17:23 +03:00
phys_addr_t mem_reloc ;
2018-06-15 01:28:02 +03:00
phys_addr_t min_addr = PHYS_ADDR_MAX ;
2017-01-27 13:17:23 +03:00
phys_addr_t max_addr = 0 ;
2019-06-22 04:21:45 +03:00
size_t metadata_len ;
2016-06-21 00:28:41 +03:00
size_t fw_name_len ;
2017-02-16 01:00:41 +03:00
ssize_t offset ;
2019-06-22 04:21:45 +03:00
void * metadata ;
2016-06-21 00:28:41 +03:00
char * fw_name ;
2017-01-27 13:17:23 +03:00
bool relocate = false ;
2016-06-21 00:28:41 +03:00
void * ptr ;
2019-06-22 04:21:45 +03:00
int ret = 0 ;
2016-06-21 00:28:41 +03:00
int i ;
2017-01-27 13:17:23 +03:00
if ( ! fw | | ! mem_region | | ! mem_phys | | ! mem_size )
return - EINVAL ;
2016-06-21 00:28:41 +03:00
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
fw_name_len = strlen ( firmware ) ;
if ( fw_name_len < = 4 )
return - EINVAL ;
fw_name = kstrdup ( firmware , GFP_KERNEL ) ;
if ( ! fw_name )
return - ENOMEM ;
2018-06-04 23:30:35 +03:00
if ( pas_init ) {
2019-06-22 04:21:45 +03:00
metadata = qcom_mdt_read_metadata ( fw , & metadata_len ) ;
if ( IS_ERR ( metadata ) ) {
ret = PTR_ERR ( metadata ) ;
goto out ;
}
ret = qcom_scm_pas_init_image ( pas_id , metadata , metadata_len ) ;
kfree ( metadata ) ;
2018-06-04 23:30:35 +03:00
if ( ret ) {
dev_err ( dev , " invalid firmware metadata \n " ) ;
goto out ;
}
2017-01-27 13:17:23 +03:00
}
2016-06-21 00:28:41 +03:00
for ( i = 0 ; i < ehdr - > e_phnum ; i + + ) {
phdr = & phdrs [ i ] ;
2017-01-27 13:17:23 +03:00
if ( ! mdt_phdr_valid ( phdr ) )
2016-06-21 00:28:41 +03:00
continue ;
2017-01-27 13:17:23 +03:00
if ( phdr - > p_flags & QCOM_MDT_RELOCATABLE )
relocate = true ;
if ( phdr - > p_paddr < min_addr )
min_addr = phdr - > p_paddr ;
if ( phdr - > p_paddr + phdr - > p_memsz > max_addr )
max_addr = ALIGN ( phdr - > p_paddr + phdr - > p_memsz , SZ_4K ) ;
}
if ( relocate ) {
2018-06-04 23:30:35 +03:00
if ( pas_init ) {
ret = qcom_scm_pas_mem_setup ( pas_id , mem_phys ,
max_addr - min_addr ) ;
if ( ret ) {
dev_err ( dev , " unable to setup relocation \n " ) ;
goto out ;
}
2017-01-27 13:17:23 +03:00
}
/*
* The image is relocatable , so offset each segment based on
* the lowest segment address .
*/
mem_reloc = min_addr ;
} else {
/*
* Image is not relocatable , so offset each segment based on
* the allocated physical chunk of memory .
*/
mem_reloc = mem_phys ;
}
2016-06-21 00:28:41 +03:00
2017-01-27 13:17:23 +03:00
for ( i = 0 ; i < ehdr - > e_phnum ; i + + ) {
phdr = & phdrs [ i ] ;
if ( ! mdt_phdr_valid ( phdr ) )
2016-06-21 00:28:41 +03:00
continue ;
2017-01-27 13:17:23 +03:00
offset = phdr - > p_paddr - mem_reloc ;
if ( offset < 0 | | offset + phdr - > p_memsz > mem_size ) {
dev_err ( dev , " segment outside memory range \n " ) ;
2016-06-21 00:28:41 +03:00
ret = - EINVAL ;
break ;
}
2017-01-27 13:17:23 +03:00
ptr = mem_region + offset ;
2019-06-22 04:21:45 +03:00
if ( phdr - > p_filesz & & phdr - > p_offset < fw - > size ) {
/* Firmware is large enough to be non-split */
if ( phdr - > p_offset + phdr - > p_filesz > fw - > size ) {
dev_err ( dev ,
" failed to load segment %d from truncated file %s \n " ,
i , firmware ) ;
ret = - EINVAL ;
break ;
}
memcpy ( ptr , fw - > data + phdr - > p_offset , phdr - > p_filesz ) ;
} else if ( phdr - > p_filesz ) {
/* Firmware not large enough, load split-out segments */
2016-06-21 00:28:41 +03:00
sprintf ( fw_name + fw_name_len - 3 , " b%02d " , i ) ;
2017-06-26 23:36:59 +03:00
ret = request_firmware_into_buf ( & seg_fw , fw_name , dev ,
ptr , phdr - > p_filesz ) ;
2016-06-21 00:28:41 +03:00
if ( ret ) {
2017-01-27 13:17:23 +03:00
dev_err ( dev , " failed to load %s \n " , fw_name ) ;
2016-06-21 00:28:41 +03:00
break ;
}
2017-01-27 13:06:36 +03:00
release_firmware ( seg_fw ) ;
2016-06-21 00:28:41 +03:00
}
if ( phdr - > p_memsz > phdr - > p_filesz )
memset ( ptr + phdr - > p_filesz , 0 , phdr - > p_memsz - phdr - > p_filesz ) ;
}
2018-01-06 03:04:19 +03:00
if ( reloc_base )
* reloc_base = mem_reloc ;
2017-01-27 13:17:23 +03:00
out :
2016-06-21 00:28:41 +03:00
kfree ( fw_name ) ;
return ret ;
}
2018-06-04 23:30:35 +03:00
/**
* qcom_mdt_load ( ) - load the firmware which header is loaded as fw
* @ dev : device handle to associate resources with
* @ fw : firmware object for the mdt file
* @ firmware : name of the firmware , for construction of segment file names
* @ pas_id : PAS identifier
* @ mem_region : allocated memory region to load firmware into
* @ mem_phys : physical address of allocated memory region
* @ mem_size : size of the allocated memory region
* @ reloc_base : adjusted physical address after relocation
*
* Returns 0 on success , negative errno otherwise .
*/
int qcom_mdt_load ( struct device * dev , const struct firmware * fw ,
const char * firmware , int pas_id , void * mem_region ,
phys_addr_t mem_phys , size_t mem_size ,
phys_addr_t * reloc_base )
{
return __qcom_mdt_load ( dev , fw , firmware , pas_id , mem_region , mem_phys ,
mem_size , reloc_base , true ) ;
}
2016-06-21 00:28:41 +03:00
EXPORT_SYMBOL_GPL ( qcom_mdt_load ) ;
2018-06-04 23:30:35 +03:00
/**
* qcom_mdt_load_no_init ( ) - load the firmware which header is loaded as fw
* @ dev : device handle to associate resources with
* @ fw : firmware object for the mdt file
* @ firmware : name of the firmware , for construction of segment file names
* @ pas_id : PAS identifier
* @ mem_region : allocated memory region to load firmware into
* @ mem_phys : physical address of allocated memory region
* @ mem_size : size of the allocated memory region
* @ reloc_base : adjusted physical address after relocation
*
* Returns 0 on success , negative errno otherwise .
*/
int qcom_mdt_load_no_init ( struct device * dev , const struct firmware * fw ,
const char * firmware , int pas_id ,
void * mem_region , phys_addr_t mem_phys ,
size_t mem_size , phys_addr_t * reloc_base )
{
return __qcom_mdt_load ( dev , fw , firmware , pas_id , mem_region , mem_phys ,
mem_size , reloc_base , false ) ;
}
EXPORT_SYMBOL_GPL ( qcom_mdt_load_no_init ) ;
2016-06-21 00:28:41 +03:00
MODULE_DESCRIPTION ( " Firmware parser for Qualcomm MDT format " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;