2013-03-22 18:34:03 +04:00
/*
* Tegra host1x Job
*
* Copyright ( c ) 2010 - 2013 , NVIDIA Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope 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 , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/dma-mapping.h>
# include <linux/err.h>
# include <linux/kref.h>
# include <linux/module.h>
# include <linux/scatterlist.h>
# include <linux/slab.h>
# include <linux/vmalloc.h>
# include <trace/events/host1x.h>
# include "channel.h"
# include "dev.h"
# include "host1x_bo.h"
# include "job.h"
# include "syncpt.h"
struct host1x_job * host1x_job_alloc ( struct host1x_channel * ch ,
u32 num_cmdbufs , u32 num_relocs ,
u32 num_waitchks )
{
struct host1x_job * job = NULL ;
unsigned int num_unpins = num_cmdbufs + num_relocs ;
u64 total ;
void * mem ;
/* Check that we're not going to overflow */
total = sizeof ( struct host1x_job ) +
num_relocs * sizeof ( struct host1x_reloc ) +
num_unpins * sizeof ( struct host1x_job_unpin_data ) +
num_waitchks * sizeof ( struct host1x_waitchk ) +
num_cmdbufs * sizeof ( struct host1x_job_gather ) +
num_unpins * sizeof ( dma_addr_t ) +
num_unpins * sizeof ( u32 * ) ;
if ( total > ULONG_MAX )
return NULL ;
mem = job = kzalloc ( total , GFP_KERNEL ) ;
if ( ! job )
return NULL ;
kref_init ( & job - > ref ) ;
job - > channel = ch ;
/* Redistribute memory to the structs */
mem + = sizeof ( struct host1x_job ) ;
job - > relocarray = num_relocs ? mem : NULL ;
mem + = num_relocs * sizeof ( struct host1x_reloc ) ;
job - > unpins = num_unpins ? mem : NULL ;
mem + = num_unpins * sizeof ( struct host1x_job_unpin_data ) ;
job - > waitchk = num_waitchks ? mem : NULL ;
mem + = num_waitchks * sizeof ( struct host1x_waitchk ) ;
job - > gathers = num_cmdbufs ? mem : NULL ;
mem + = num_cmdbufs * sizeof ( struct host1x_job_gather ) ;
job - > addr_phys = num_unpins ? mem : NULL ;
job - > reloc_addr_phys = job - > addr_phys ;
job - > gather_addr_phys = & job - > addr_phys [ num_relocs ] ;
return job ;
}
struct host1x_job * host1x_job_get ( struct host1x_job * job )
{
kref_get ( & job - > ref ) ;
return job ;
}
static void job_free ( struct kref * ref )
{
struct host1x_job * job = container_of ( ref , struct host1x_job , ref ) ;
kfree ( job ) ;
}
void host1x_job_put ( struct host1x_job * job )
{
kref_put ( & job - > ref , job_free ) ;
}
void host1x_job_add_gather ( struct host1x_job * job , struct host1x_bo * bo ,
u32 words , u32 offset )
{
struct host1x_job_gather * cur_gather = & job - > gathers [ job - > num_gathers ] ;
cur_gather - > words = words ;
cur_gather - > bo = bo ;
cur_gather - > offset = offset ;
job - > num_gathers + + ;
}
/*
* NULL an already satisfied WAIT_SYNCPT host method , by patching its
* args in the command stream . The method data is changed to reference
* a reserved ( never given out or incr ) HOST1X_SYNCPT_RESERVED syncpt
* with a matching threshold value of 0 , so is guaranteed to be popped
* by the host HW .
*/
static void host1x_syncpt_patch_offset ( struct host1x_syncpt * sp ,
struct host1x_bo * h , u32 offset )
{
void * patch_addr = NULL ;
/* patch the wait */
patch_addr = host1x_bo_kmap ( h , offset > > PAGE_SHIFT ) ;
if ( patch_addr ) {
host1x_syncpt_patch_wait ( sp ,
patch_addr + ( offset & ~ PAGE_MASK ) ) ;
host1x_bo_kunmap ( h , offset > > PAGE_SHIFT , patch_addr ) ;
} else
pr_err ( " Could not map cmdbuf for wait check \n " ) ;
}
/*
* Check driver supplied waitchk structs for syncpt thresholds
* that have already been satisfied and NULL the comparison ( to
* avoid a wrap condition in the HW ) .
*/
static int do_waitchks ( struct host1x_job * job , struct host1x * host ,
struct host1x_bo * patch )
{
int i ;
/* compare syncpt vs wait threshold */
for ( i = 0 ; i < job - > num_waitchk ; i + + ) {
struct host1x_waitchk * wait = & job - > waitchk [ i ] ;
struct host1x_syncpt * sp =
host1x_syncpt_get ( host , wait - > syncpt_id ) ;
/* validate syncpt id */
if ( wait - > syncpt_id > host1x_syncpt_nb_pts ( host ) )
continue ;
/* skip all other gathers */
if ( patch ! = wait - > bo )
continue ;
trace_host1x_syncpt_wait_check ( wait - > bo , wait - > offset ,
wait - > syncpt_id , wait - > thresh ,
host1x_syncpt_read_min ( sp ) ) ;
if ( host1x_syncpt_is_expired ( sp , wait - > thresh ) ) {
dev_dbg ( host - > dev ,
" drop WAIT id %d (%s) thresh 0x%x, min 0x%x \n " ,
wait - > syncpt_id , sp - > name , wait - > thresh ,
host1x_syncpt_read_min ( sp ) ) ;
host1x_syncpt_patch_offset ( sp , patch , wait - > offset ) ;
}
wait - > bo = NULL ;
}
return 0 ;
}
static unsigned int pin_job ( struct host1x_job * job )
{
unsigned int i ;
job - > num_unpins = 0 ;
for ( i = 0 ; i < job - > num_relocs ; i + + ) {
struct host1x_reloc * reloc = & job - > relocarray [ i ] ;
struct sg_table * sgt ;
dma_addr_t phys_addr ;
reloc - > target = host1x_bo_get ( reloc - > target ) ;
if ( ! reloc - > target )
goto unpin ;
phys_addr = host1x_bo_pin ( reloc - > target , & sgt ) ;
if ( ! phys_addr )
goto unpin ;
job - > addr_phys [ job - > num_unpins ] = phys_addr ;
job - > unpins [ job - > num_unpins ] . bo = reloc - > target ;
job - > unpins [ job - > num_unpins ] . sgt = sgt ;
job - > num_unpins + + ;
}
for ( i = 0 ; i < job - > num_gathers ; i + + ) {
struct host1x_job_gather * g = & job - > gathers [ i ] ;
struct sg_table * sgt ;
dma_addr_t phys_addr ;
g - > bo = host1x_bo_get ( g - > bo ) ;
if ( ! g - > bo )
goto unpin ;
phys_addr = host1x_bo_pin ( g - > bo , & sgt ) ;
if ( ! phys_addr )
goto unpin ;
job - > addr_phys [ job - > num_unpins ] = phys_addr ;
job - > unpins [ job - > num_unpins ] . bo = g - > bo ;
job - > unpins [ job - > num_unpins ] . sgt = sgt ;
job - > num_unpins + + ;
}
return job - > num_unpins ;
unpin :
host1x_job_unpin ( job ) ;
return 0 ;
}
static unsigned int do_relocs ( struct host1x_job * job , struct host1x_bo * cmdbuf )
{
int i = 0 ;
u32 last_page = ~ 0 ;
void * cmdbuf_page_addr = NULL ;
/* pin & patch the relocs for one gather */
2013-05-29 14:26:05 +04:00
for ( i = 0 ; i < job - > num_relocs ; i + + ) {
2013-03-22 18:34:03 +04:00
struct host1x_reloc * reloc = & job - > relocarray [ i ] ;
u32 reloc_addr = ( job - > reloc_addr_phys [ i ] +
reloc - > target_offset ) > > reloc - > shift ;
u32 * target ;
/* skip all other gathers */
2013-05-29 14:26:05 +04:00
if ( cmdbuf ! = reloc - > cmdbuf )
2013-03-22 18:34:03 +04:00
continue ;
if ( last_page ! = reloc - > cmdbuf_offset > > PAGE_SHIFT ) {
if ( cmdbuf_page_addr )
host1x_bo_kunmap ( cmdbuf , last_page ,
cmdbuf_page_addr ) ;
cmdbuf_page_addr = host1x_bo_kmap ( cmdbuf ,
reloc - > cmdbuf_offset > > PAGE_SHIFT ) ;
last_page = reloc - > cmdbuf_offset > > PAGE_SHIFT ;
if ( unlikely ( ! cmdbuf_page_addr ) ) {
pr_err ( " Could not map cmdbuf for relocation \n " ) ;
return - ENOMEM ;
}
}
target = cmdbuf_page_addr + ( reloc - > cmdbuf_offset & ~ PAGE_MASK ) ;
* target = reloc_addr ;
}
if ( cmdbuf_page_addr )
host1x_bo_kunmap ( cmdbuf , last_page , cmdbuf_page_addr ) ;
return 0 ;
}
2013-05-29 14:26:03 +04:00
static bool check_reloc ( struct host1x_reloc * reloc , struct host1x_bo * cmdbuf ,
2013-03-22 18:34:03 +04:00
unsigned int offset )
{
offset * = sizeof ( u32 ) ;
if ( reloc - > cmdbuf ! = cmdbuf | | reloc - > cmdbuf_offset ! = offset )
2013-05-29 14:26:03 +04:00
return false ;
2013-03-22 18:34:03 +04:00
2013-05-29 14:26:03 +04:00
return true ;
2013-03-22 18:34:03 +04:00
}
struct host1x_firewall {
struct host1x_job * job ;
struct device * dev ;
unsigned int num_relocs ;
struct host1x_reloc * reloc ;
struct host1x_bo * cmdbuf_id ;
unsigned int offset ;
u32 words ;
u32 class ;
u32 reg ;
u32 mask ;
u32 count ;
} ;
static int check_mask ( struct host1x_firewall * fw )
{
u32 mask = fw - > mask ;
u32 reg = fw - > reg ;
while ( mask ) {
if ( fw - > words = = 0 )
return - EINVAL ;
if ( mask & 1 ) {
if ( fw - > job - > is_addr_reg ( fw - > dev , fw - > class , reg ) ) {
2013-05-29 14:26:03 +04:00
if ( ! fw - > num_relocs )
return - EINVAL ;
if ( ! check_reloc ( fw - > reloc , fw - > cmdbuf_id ,
fw - > offset ) )
2013-03-22 18:34:03 +04:00
return - EINVAL ;
fw - > reloc + + ;
fw - > num_relocs - - ;
}
fw - > words - - ;
fw - > offset + + ;
}
mask > > = 1 ;
reg + + ;
}
return 0 ;
}
static int check_incr ( struct host1x_firewall * fw )
{
u32 count = fw - > count ;
u32 reg = fw - > reg ;
2013-05-29 14:26:02 +04:00
while ( count ) {
2013-03-22 18:34:03 +04:00
if ( fw - > words = = 0 )
return - EINVAL ;
if ( fw - > job - > is_addr_reg ( fw - > dev , fw - > class , reg ) ) {
2013-05-29 14:26:03 +04:00
if ( ! fw - > num_relocs )
return - EINVAL ;
if ( ! check_reloc ( fw - > reloc , fw - > cmdbuf_id , fw - > offset ) )
2013-03-22 18:34:03 +04:00
return - EINVAL ;
fw - > reloc + + ;
fw - > num_relocs - - ;
}
reg + + ;
fw - > words - - ;
fw - > offset + + ;
count - - ;
}
return 0 ;
}
static int check_nonincr ( struct host1x_firewall * fw )
{
int is_addr_reg = fw - > job - > is_addr_reg ( fw - > dev , fw - > class , fw - > reg ) ;
u32 count = fw - > count ;
while ( count ) {
if ( fw - > words = = 0 )
return - EINVAL ;
if ( is_addr_reg ) {
2013-05-29 14:26:03 +04:00
if ( ! fw - > num_relocs )
return - EINVAL ;
if ( ! check_reloc ( fw - > reloc , fw - > cmdbuf_id , fw - > offset ) )
2013-03-22 18:34:03 +04:00
return - EINVAL ;
fw - > reloc + + ;
fw - > num_relocs - - ;
}
fw - > words - - ;
fw - > offset + + ;
count - - ;
}
return 0 ;
}
2013-05-29 14:26:04 +04:00
static int validate ( struct host1x_firewall * fw , struct host1x_job_gather * g )
2013-03-22 18:34:03 +04:00
{
2013-05-29 14:26:05 +04:00
u32 * cmdbuf_base = ( u32 * ) fw - > job - > gather_copy_mapped +
( g - > offset / sizeof ( u32 ) ) ;
2013-03-22 18:34:03 +04:00
int err = 0 ;
2013-05-29 14:26:04 +04:00
if ( ! fw - > job - > is_addr_reg )
2013-03-22 18:34:03 +04:00
return 0 ;
2013-05-29 14:26:04 +04:00
fw - > words = g - > words ;
fw - > cmdbuf_id = g - > bo ;
fw - > offset = 0 ;
2013-03-22 18:34:03 +04:00
2013-05-29 14:26:04 +04:00
while ( fw - > words & & ! err ) {
u32 word = cmdbuf_base [ fw - > offset ] ;
2013-03-22 18:34:03 +04:00
u32 opcode = ( word & 0xf0000000 ) > > 28 ;
2013-05-29 14:26:04 +04:00
fw - > mask = 0 ;
fw - > reg = 0 ;
fw - > count = 0 ;
fw - > words - - ;
fw - > offset + + ;
2013-03-22 18:34:03 +04:00
switch ( opcode ) {
case 0 :
2013-05-29 14:26:04 +04:00
fw - > class = word > > 6 & 0x3ff ;
fw - > mask = word & 0x3f ;
fw - > reg = word > > 16 & 0xfff ;
err = check_mask ( fw ) ;
2013-03-22 18:34:03 +04:00
if ( err )
goto out ;
break ;
case 1 :
2013-05-29 14:26:04 +04:00
fw - > reg = word > > 16 & 0xfff ;
fw - > count = word & 0xffff ;
err = check_incr ( fw ) ;
2013-03-22 18:34:03 +04:00
if ( err )
goto out ;
break ;
case 2 :
2013-05-29 14:26:04 +04:00
fw - > reg = word > > 16 & 0xfff ;
fw - > count = word & 0xffff ;
err = check_nonincr ( fw ) ;
2013-03-22 18:34:03 +04:00
if ( err )
goto out ;
break ;
case 3 :
2013-05-29 14:26:04 +04:00
fw - > mask = word & 0xffff ;
fw - > reg = word > > 16 & 0xfff ;
err = check_mask ( fw ) ;
2013-03-22 18:34:03 +04:00
if ( err )
goto out ;
break ;
case 4 :
case 5 :
case 14 :
break ;
default :
err = - EINVAL ;
break ;
}
}
/* No relocs should remain at this point */
2013-05-29 14:26:04 +04:00
if ( fw - > num_relocs )
2013-03-22 18:34:03 +04:00
err = - EINVAL ;
out :
return err ;
}
static inline int copy_gathers ( struct host1x_job * job , struct device * dev )
{
2013-05-29 14:26:05 +04:00
struct host1x_firewall fw ;
2013-03-22 18:34:03 +04:00
size_t size = 0 ;
size_t offset = 0 ;
int i ;
2013-05-29 14:26:05 +04:00
fw . job = job ;
fw . dev = dev ;
fw . reloc = job - > relocarray ;
fw . num_relocs = job - > num_relocs ;
fw . class = 0 ;
2013-03-22 18:34:03 +04:00
for ( i = 0 ; i < job - > num_gathers ; i + + ) {
struct host1x_job_gather * g = & job - > gathers [ i ] ;
size + = g - > words * sizeof ( u32 ) ;
}
job - > gather_copy_mapped = dma_alloc_writecombine ( dev , size ,
& job - > gather_copy ,
GFP_KERNEL ) ;
if ( ! job - > gather_copy_mapped ) {
int err = PTR_ERR ( job - > gather_copy_mapped ) ;
job - > gather_copy_mapped = NULL ;
return err ;
}
job - > gather_copy_size = size ;
for ( i = 0 ; i < job - > num_gathers ; i + + ) {
struct host1x_job_gather * g = & job - > gathers [ i ] ;
void * gather ;
2013-05-29 14:26:05 +04:00
/* Copy the gather */
2013-03-22 18:34:03 +04:00
gather = host1x_bo_mmap ( g - > bo ) ;
memcpy ( job - > gather_copy_mapped + offset , gather + g - > offset ,
g - > words * sizeof ( u32 ) ) ;
host1x_bo_munmap ( g - > bo , gather ) ;
2013-05-29 14:26:05 +04:00
/* Store the location in the buffer */
2013-03-22 18:34:03 +04:00
g - > base = job - > gather_copy ;
g - > offset = offset ;
2013-05-29 14:26:05 +04:00
/* Validate the job */
if ( validate ( & fw , g ) )
return - EINVAL ;
2013-03-22 18:34:03 +04:00
offset + = g - > words * sizeof ( u32 ) ;
}
return 0 ;
}
int host1x_job_pin ( struct host1x_job * job , struct device * dev )
{
int err ;
unsigned int i , j ;
struct host1x * host = dev_get_drvdata ( dev - > parent ) ;
DECLARE_BITMAP ( waitchk_mask , host1x_syncpt_nb_pts ( host ) ) ;
bitmap_zero ( waitchk_mask , host1x_syncpt_nb_pts ( host ) ) ;
for ( i = 0 ; i < job - > num_waitchk ; i + + ) {
u32 syncpt_id = job - > waitchk [ i ] . syncpt_id ;
if ( syncpt_id < host1x_syncpt_nb_pts ( host ) )
set_bit ( syncpt_id , waitchk_mask ) ;
}
/* get current syncpt values for waitchk */
for_each_set_bit ( i , waitchk_mask , host1x_syncpt_nb_pts ( host ) )
host1x_syncpt_load ( host - > syncpt + i ) ;
/* pin memory */
err = pin_job ( job ) ;
if ( ! err )
goto out ;
/* patch gathers */
for ( i = 0 ; i < job - > num_gathers ; i + + ) {
struct host1x_job_gather * g = & job - > gathers [ i ] ;
/* process each gather mem only once */
if ( g - > handled )
continue ;
g - > base = job - > gather_addr_phys [ i ] ;
for ( j = 0 ; j < job - > num_gathers ; j + + )
if ( job - > gathers [ j ] . bo = = g - > bo )
job - > gathers [ j ] . handled = true ;
2013-05-29 14:26:05 +04:00
err = do_relocs ( job , g - > bo ) ;
2013-03-22 18:34:03 +04:00
if ( err )
2013-05-29 14:26:05 +04:00
break ;
2013-03-22 18:34:03 +04:00
2013-05-29 14:26:05 +04:00
err = do_waitchks ( job , host , g - > bo ) ;
2013-03-22 18:34:03 +04:00
if ( err )
break ;
}
if ( IS_ENABLED ( CONFIG_TEGRA_HOST1X_FIREWALL ) & & ! err ) {
err = copy_gathers ( job , dev ) ;
if ( err ) {
host1x_job_unpin ( job ) ;
return err ;
}
}
out :
wmb ( ) ;
return err ;
}
void host1x_job_unpin ( struct host1x_job * job )
{
unsigned int i ;
for ( i = 0 ; i < job - > num_unpins ; i + + ) {
struct host1x_job_unpin_data * unpin = & job - > unpins [ i ] ;
host1x_bo_unpin ( unpin - > bo , unpin - > sgt ) ;
host1x_bo_put ( unpin - > bo ) ;
}
job - > num_unpins = 0 ;
if ( job - > gather_copy_size )
dma_free_writecombine ( job - > channel - > dev , job - > gather_copy_size ,
job - > gather_copy_mapped ,
job - > gather_copy ) ;
}
/*
* Debug routine used to dump job entries
*/
void host1x_job_dump ( struct device * dev , struct host1x_job * job )
{
dev_dbg ( dev , " SYNCPT_ID %d \n " , job - > syncpt_id ) ;
dev_dbg ( dev , " SYNCPT_VAL %d \n " , job - > syncpt_end ) ;
dev_dbg ( dev , " FIRST_GET 0x%x \n " , job - > first_get ) ;
dev_dbg ( dev , " TIMEOUT %d \n " , job - > timeout ) ;
dev_dbg ( dev , " NUM_SLOTS %d \n " , job - > num_slots ) ;
dev_dbg ( dev , " NUM_HANDLES %d \n " , job - > num_unpins ) ;
}