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 .
*
* 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 .
*/
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 ;
phys_addr_t min_addr = ( phys_addr_t ) ULLONG_MAX ;
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
/**
2017-01-27 13:17:23 +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
2018-01-06 03:04:19 +03:00
* @ reloc_base : adjusted physical address after relocation
2016-06-21 00:28:41 +03:00
*
* Returns 0 on success , negative errno otherwise .
*/
2017-01-27 13:17:23 +03:00
int qcom_mdt_load ( struct device * dev , const struct firmware * fw ,
const char * firmware , int pas_id , void * mem_region ,
2018-01-06 03:04:19 +03:00
phys_addr_t mem_phys , size_t mem_size ,
phys_addr_t * reloc_base )
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 ;
phys_addr_t min_addr = ( phys_addr_t ) ULLONG_MAX ;
phys_addr_t max_addr = 0 ;
2016-06-21 00:28:41 +03:00
size_t fw_name_len ;
2017-02-16 01:00:41 +03:00
ssize_t offset ;
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 ;
int ret ;
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 ;
2017-01-27 13:17:23 +03:00
ret = qcom_scm_pas_init_image ( pas_id , fw - > data , fw - > size ) ;
if ( ret ) {
dev_err ( dev , " invalid firmware metadata \n " ) ;
goto out ;
}
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 ) {
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 ;
}
/*
* 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 ;
2016-06-21 00:28:41 +03:00
if ( phdr - > p_filesz ) {
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 ;
}
EXPORT_SYMBOL_GPL ( qcom_mdt_load ) ;
MODULE_DESCRIPTION ( " Firmware parser for Qualcomm MDT format " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;