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>
2023-02-04 00:09:52 +03:00
# include <linux/firmware/qcom/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 ;
}
2022-01-28 05:55:02 +03:00
static ssize_t mdt_load_split_segment ( void * ptr , const struct elf32_phdr * phdrs ,
unsigned int segment , const char * fw_name ,
struct device * dev )
{
const struct elf32_phdr * phdr = & phdrs [ segment ] ;
const struct firmware * seg_fw ;
char * seg_name ;
ssize_t ret ;
if ( strlen ( fw_name ) < 4 )
return - EINVAL ;
seg_name = kstrdup ( fw_name , GFP_KERNEL ) ;
if ( ! seg_name )
return - ENOMEM ;
sprintf ( seg_name + strlen ( fw_name ) - 3 , " b%02d " , segment ) ;
ret = request_firmware_into_buf ( & seg_fw , seg_name , dev ,
ptr , phdr - > p_filesz ) ;
if ( ret ) {
dev_err ( dev , " error %zd loading %s \n " , ret , seg_name ) ;
kfree ( seg_name ) ;
return ret ;
}
if ( seg_fw - > size ! = phdr - > p_filesz ) {
dev_err ( dev ,
" failed to load segment %d from truncated file %s \n " ,
segment , seg_name ) ;
ret = - EINVAL ;
}
release_firmware ( seg_fw ) ;
kfree ( seg_name ) ;
return ret ;
}
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
2022-05-19 10:33:01 +03:00
* @ fw_name : name of the firmware , for construction of segment file names
* @ dev : device handle to associate resources with
2019-06-22 04:21:45 +03:00
*
* 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 ( )
*/
2022-01-28 05:55:03 +03:00
void * qcom_mdt_read_metadata ( const struct firmware * fw , size_t * data_len ,
const char * fw_name , struct device * dev )
2019-06-22 04:21:45 +03:00
{
const struct elf32_phdr * phdrs ;
const struct elf32_hdr * ehdr ;
2022-01-28 05:55:04 +03:00
unsigned int hash_segment = 0 ;
2019-06-22 04:21:45 +03:00
size_t hash_offset ;
size_t hash_size ;
size_t ehdr_size ;
2022-01-28 05:55:04 +03:00
unsigned int i ;
2022-01-28 05:55:03 +03:00
ssize_t ret ;
2019-06-22 04:21:45 +03:00
void * data ;
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
if ( ehdr - > e_phnum < 2 )
return ERR_PTR ( - EINVAL ) ;
2021-08-28 10:02:02 +03:00
if ( phdrs [ 0 ] . p_type = = PT_LOAD )
2019-06-22 04:21:45 +03:00
return ERR_PTR ( - EINVAL ) ;
2022-01-28 05:55:04 +03:00
for ( i = 1 ; i < ehdr - > e_phnum ; i + + ) {
if ( ( phdrs [ i ] . p_flags & QCOM_MDT_TYPE_MASK ) = = QCOM_MDT_TYPE_HASH ) {
hash_segment = i ;
break ;
}
}
if ( ! hash_segment ) {
dev_err ( dev , " no hash segment found in %s \n " , fw_name ) ;
2019-06-22 04:21:45 +03:00
return ERR_PTR ( - EINVAL ) ;
2022-01-28 05:55:04 +03:00
}
2019-06-22 04:21:45 +03:00
ehdr_size = phdrs [ 0 ] . p_filesz ;
2022-01-28 05:55:04 +03:00
hash_size = phdrs [ hash_segment ] . p_filesz ;
2019-06-22 04:21:45 +03:00
data = kmalloc ( ehdr_size + hash_size , GFP_KERNEL ) ;
if ( ! data )
return ERR_PTR ( - ENOMEM ) ;
2022-01-28 05:55:03 +03:00
/* Copy ELF header */
memcpy ( data , fw - > data , ehdr_size ) ;
if ( ehdr_size + hash_size = = fw - > size ) {
/* Firmware is split and hash is packed following the ELF header */
2019-06-22 04:21:45 +03:00
hash_offset = phdrs [ 0 ] . p_filesz ;
2022-01-28 05:55:03 +03:00
memcpy ( data + ehdr_size , fw - > data + hash_offset , hash_size ) ;
2022-01-28 05:55:04 +03:00
} else if ( phdrs [ hash_segment ] . p_offset + hash_size < = fw - > size ) {
2022-01-28 05:55:03 +03:00
/* Hash is in its own segment, but within the loaded file */
2022-01-28 05:55:04 +03:00
hash_offset = phdrs [ hash_segment ] . p_offset ;
2022-01-28 05:55:03 +03:00
memcpy ( data + ehdr_size , fw - > data + hash_offset , hash_size ) ;
} else {
/* Hash is in its own segment, beyond the loaded file */
2022-01-28 05:55:04 +03:00
ret = mdt_load_split_segment ( data + ehdr_size , phdrs , hash_segment , fw_name , dev ) ;
2022-01-28 05:55:03 +03:00
if ( ret ) {
kfree ( data ) ;
return ERR_PTR ( ret ) ;
}
}
2019-06-22 04:21:45 +03:00
* data_len = ehdr_size + hash_size ;
return data ;
}
EXPORT_SYMBOL_GPL ( qcom_mdt_read_metadata ) ;
2022-01-28 05:55:08 +03:00
/**
* qcom_mdt_pas_init ( ) - initialize PAS region for firmware loading
* @ dev : device handle to associate resources with
* @ fw : firmware object for the mdt file
2022-05-19 10:33:01 +03:00
* @ fw_name : name of the firmware , for construction of segment file names
2022-01-28 05:55:08 +03:00
* @ pas_id : PAS identifier
* @ mem_phys : physical address of allocated memory region
* @ ctx : PAS metadata context , to be released by caller
*
* Returns 0 on success , negative errno otherwise .
*/
int qcom_mdt_pas_init ( struct device * dev , const struct firmware * fw ,
const char * fw_name , int pas_id , phys_addr_t mem_phys ,
struct qcom_scm_pas_metadata * ctx )
{
const struct elf32_phdr * phdrs ;
const struct elf32_phdr * phdr ;
const struct elf32_hdr * ehdr ;
phys_addr_t min_addr = PHYS_ADDR_MAX ;
phys_addr_t max_addr = 0 ;
2023-05-26 14:55:11 +03:00
bool relocate = false ;
2022-01-28 05:55:08 +03:00
size_t metadata_len ;
void * metadata ;
int ret ;
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 ] ;
if ( ! mdt_phdr_valid ( phdr ) )
continue ;
2023-05-26 14:55:11 +03:00
if ( phdr - > p_flags & QCOM_MDT_RELOCATABLE )
relocate = true ;
2022-01-28 05:55:08 +03:00
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 ) ;
}
metadata = qcom_mdt_read_metadata ( fw , & metadata_len , fw_name , dev ) ;
if ( IS_ERR ( metadata ) ) {
ret = PTR_ERR ( metadata ) ;
dev_err ( dev , " error %d reading firmware %s metadata \n " , ret , fw_name ) ;
goto out ;
}
ret = qcom_scm_pas_init_image ( pas_id , metadata , metadata_len , ctx ) ;
kfree ( metadata ) ;
if ( ret ) {
/* Invalid firmware metadata */
dev_err ( dev , " error %d initializing firmware %s \n " , ret , fw_name ) ;
goto out ;
}
2023-05-26 14:55:11 +03:00
if ( relocate ) {
ret = qcom_scm_pas_mem_setup ( pas_id , mem_phys , max_addr - min_addr ) ;
if ( ret ) {
/* Unable to set up relocation */
dev_err ( dev , " error %d setting up firmware %s \n " , ret , fw_name ) ;
goto out ;
}
2022-01-28 05:55:08 +03:00
}
out :
return ret ;
}
EXPORT_SYMBOL_GPL ( qcom_mdt_pas_init ) ;
2023-05-09 03:18:21 +03:00
static bool qcom_mdt_bins_are_split ( const struct firmware * fw , const char * fw_name )
{
const struct elf32_phdr * phdrs ;
const struct elf32_hdr * ehdr ;
uint64_t seg_start , seg_end ;
int i ;
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
for ( i = 0 ; i < ehdr - > e_phnum ; i + + ) {
2023-06-13 00:58:04 +03:00
/*
* The size of the MDT file is not padded to include any
* zero - sized segments at the end . Ignore these , as they should
* not affect the decision about image being split or not .
*/
if ( ! phdrs [ i ] . p_filesz )
continue ;
2023-05-09 03:18:21 +03:00
seg_start = phdrs [ i ] . p_offset ;
seg_end = phdrs [ i ] . p_offset + phdrs [ i ] . p_filesz ;
if ( seg_start > fw - > size | | seg_end > fw - > size )
return true ;
}
return false ;
}
2018-06-04 23:30:35 +03:00
static int __qcom_mdt_load ( struct device * dev , const struct firmware * fw ,
2022-01-28 05:55:02 +03:00
const char * fw_name , int pas_id , void * mem_region ,
2018-06-04 23:30:35 +03:00
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: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-02-16 01:00:41 +03:00
ssize_t offset ;
2017-01-27 13:17:23 +03:00
bool relocate = false ;
2023-05-09 03:18:21 +03:00
bool is_split ;
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 ;
2023-05-09 03:18:21 +03:00
is_split = qcom_mdt_bins_are_split ( fw , fw_name ) ;
2016-06-21 00:28:41 +03:00
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
2022-01-28 05:55:06 +03:00
for ( i = 0 ; i < ehdr - > e_phnum ; i + + ) {
phdr = & phdrs [ i ] ;
if ( ! mdt_phdr_valid ( phdr ) )
continue ;
if ( phdr - > p_flags & QCOM_MDT_RELOCATABLE )
relocate = true ;
if ( phdr - > p_paddr < min_addr )
min_addr = phdr - > p_paddr ;
2022-01-28 05:55:07 +03:00
}
2017-01-27 13:17:23 +03:00
2022-01-28 05:55:07 +03:00
if ( relocate ) {
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 ;
}
2021-01-08 02:31:19 +03:00
if ( phdr - > p_filesz > phdr - > p_memsz ) {
dev_err ( dev ,
" refusing to load segment %d with p_filesz > p_memsz \n " ,
i ) ;
ret = - EINVAL ;
break ;
}
2017-01-27 13:17:23 +03:00
ptr = mem_region + offset ;
2023-05-09 03:18:21 +03:00
if ( phdr - > p_filesz & & ! is_split ) {
2019-06-22 04:21:45 +03:00
/* Firmware is large enough to be non-split */
if ( phdr - > p_offset + phdr - > p_filesz > fw - > size ) {
2021-03-15 17:44:55 +03:00
dev_err ( dev , " file %s segment %d would be truncated \n " ,
fw_name , i ) ;
2019-06-22 04:21:45 +03:00
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 */
2022-01-28 05:55:02 +03:00
ret = mdt_load_split_segment ( ptr , phdrs , i , fw_name , dev ) ;
if ( ret )
2016-06-21 00:28:41 +03:00
break ;
}
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 ;
2016-06-21 00:28:41 +03:00
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 )
{
2022-01-28 05:55:08 +03:00
int ret ;
ret = qcom_mdt_pas_init ( dev , fw , firmware , pas_id , mem_phys , NULL ) ;
if ( ret )
return ret ;
2018-06-04 23:30:35 +03:00
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 " ) ;