2009-12-11 19:24:15 +10:00
/*
* Copyright ( C ) 2007 Ben Skeggs .
* All Rights Reserved .
*
* Permission is hereby granted , free of charge , to any person obtaining
* a copy of this software and associated documentation files ( the
* " Software " ) , to deal in the Software without restriction , including
* without limitation the rights to use , copy , modify , merge , publish ,
* distribute , sublicense , and / or sell copies of the Software , and to
* permit persons to whom the Software is furnished to do so , subject to
* the following conditions :
*
* The above copyright notice and this permission notice ( including the
* next paragraph ) shall be included in all copies or substantial
* portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .
* IN NO EVENT SHALL THE COPYRIGHT OWNER ( S ) AND / OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION
* OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
*
*/
2012-07-20 08:17:34 +10:00
# include "nouveau_drm.h"
# include "nouveau_dma.h"
2010-01-08 10:57:39 +10:00
2009-12-11 19:24:15 +10:00
void
OUT_RINGp ( struct nouveau_channel * chan , const void * data , unsigned nr_dwords )
{
bool is_iomem ;
2012-07-20 08:17:34 +10:00
u32 * mem = ttm_kmap_obj_virtual ( & chan - > push . buffer - > kmap , & is_iomem ) ;
2009-12-11 19:24:15 +10:00
mem = & mem [ chan - > dma . cur ] ;
if ( is_iomem )
memcpy_toio ( ( void __force __iomem * ) mem , data , nr_dwords * 4 ) ;
else
memcpy ( mem , data , nr_dwords * 4 ) ;
chan - > dma . cur + = nr_dwords ;
}
2010-01-15 12:08:57 +10:00
/* Fetch and adjust GPU GET pointer
*
* Returns :
* value > = 0 , the adjusted GET pointer
* - EINVAL if GET pointer currently outside main push buffer
* - EBUSY if timeout exceeded
*/
static inline int
2011-11-19 11:57:52 +01:00
READ_GET ( struct nouveau_channel * chan , uint64_t * prev_get , int * timeout )
2009-12-11 19:24:15 +10:00
{
2011-11-19 11:57:52 +01:00
uint64_t val ;
2009-12-11 19:24:15 +10:00
2015-08-20 14:54:15 +10:00
val = nvif_rd32 ( & chan - > user , chan - > user_get ) ;
2011-11-19 11:57:52 +01:00
if ( chan - > user_get_hi )
2015-08-20 14:54:15 +10:00
val | = ( uint64_t ) nvif_rd32 ( & chan - > user , chan - > user_get_hi ) < < 32 ;
2010-01-15 12:08:57 +10:00
/* reset counter as long as GET is still advancing, this is
* to avoid misdetecting a GPU lockup if the GPU happens to
* just be processing an operation that takes a long time
*/
if ( val ! = * prev_get ) {
* prev_get = val ;
* timeout = 0 ;
}
if ( ( + + * timeout & 0xff ) = = 0 ) {
2012-07-20 08:17:34 +10:00
udelay ( 1 ) ;
2010-01-15 12:08:57 +10:00
if ( * timeout > 100000 )
return - EBUSY ;
2009-12-11 19:24:15 +10:00
}
2012-07-20 08:17:34 +10:00
if ( val < chan - > push . vma . offset | |
val > chan - > push . vma . offset + ( chan - > dma . max < < 2 ) )
2010-01-15 12:08:57 +10:00
return - EINVAL ;
2012-07-20 08:17:34 +10:00
return ( val - chan - > push . vma . offset ) > > 2 ;
2009-12-11 19:24:15 +10:00
}
2010-02-11 16:37:26 +10:00
void
nv50_dma_push ( struct nouveau_channel * chan , struct nouveau_bo * bo ,
2010-02-12 10:27:35 +10:00
int delta , int length )
2010-02-11 16:37:26 +10:00
{
2015-08-20 14:54:15 +10:00
struct nouveau_cli * cli = ( void * ) chan - > user . client ;
2012-07-20 08:17:34 +10:00
struct nouveau_bo * pb = chan - > push . buffer ;
2015-01-14 15:36:34 +10:00
struct nvkm_vma * vma ;
2010-02-11 16:37:26 +10:00
int ip = ( chan - > dma . ib_put * 2 ) + chan - > dma . ib_base ;
2011-06-07 13:23:47 +10:00
u64 offset ;
2014-08-10 04:10:22 +10:00
vma = nouveau_bo_vma_find ( bo , cli - > vm ) ;
2011-06-07 13:23:47 +10:00
BUG_ON ( ! vma ) ;
offset = vma - > offset + delta ;
2010-02-11 16:37:26 +10:00
BUG_ON ( chan - > dma . ib_free < 1 ) ;
2012-07-20 08:17:34 +10:00
2010-02-12 11:11:54 +10:00
nouveau_bo_wr32 ( pb , ip + + , lower_32_bits ( offset ) ) ;
2010-02-12 10:27:35 +10:00
nouveau_bo_wr32 ( pb , ip + + , upper_32_bits ( offset ) | length < < 8 ) ;
2010-02-11 16:37:26 +10:00
chan - > dma . ib_put = ( chan - > dma . ib_put + 1 ) & chan - > dma . ib_max ;
2010-02-25 20:00:38 +01:00
2013-12-11 11:34:45 +01:00
mb ( ) ;
2010-02-25 20:00:38 +01:00
/* Flush writes. */
nouveau_bo_rd32 ( pb , 0 ) ;
2015-08-20 14:54:15 +10:00
nvif_wr32 ( & chan - > user , 0x8c , chan - > dma . ib_put ) ;
2010-02-11 16:37:26 +10:00
chan - > dma . ib_free - - ;
}
static int
nv50_dma_push_wait ( struct nouveau_channel * chan , int count )
{
uint32_t cnt = 0 , prev_get = 0 ;
while ( chan - > dma . ib_free < count ) {
2015-08-20 14:54:15 +10:00
uint32_t get = nvif_rd32 ( & chan - > user , 0x88 ) ;
2010-02-11 16:37:26 +10:00
if ( get ! = prev_get ) {
prev_get = get ;
cnt = 0 ;
}
if ( ( + + cnt & 0xff ) = = 0 ) {
DRM_UDELAY ( 1 ) ;
if ( cnt > 100000 )
return - EBUSY ;
}
chan - > dma . ib_free = get - chan - > dma . ib_put ;
if ( chan - > dma . ib_free < = 0 )
2010-09-30 09:09:42 +10:00
chan - > dma . ib_free + = chan - > dma . ib_max ;
2010-02-11 16:37:26 +10:00
}
return 0 ;
}
static int
nv50_dma_wait ( struct nouveau_channel * chan , int slots , int count )
{
2011-11-19 11:57:52 +01:00
uint64_t prev_get = 0 ;
int ret , cnt = 0 ;
2010-02-11 16:37:26 +10:00
ret = nv50_dma_push_wait ( chan , slots + 1 ) ;
if ( unlikely ( ret ) )
return ret ;
while ( chan - > dma . free < count ) {
int get = READ_GET ( chan , & prev_get , & cnt ) ;
if ( unlikely ( get < 0 ) ) {
if ( get = = - EINVAL )
continue ;
return get ;
}
if ( get < = chan - > dma . cur ) {
chan - > dma . free = chan - > dma . max - chan - > dma . cur ;
if ( chan - > dma . free > = count )
break ;
FIRE_RING ( chan ) ;
do {
get = READ_GET ( chan , & prev_get , & cnt ) ;
if ( unlikely ( get < 0 ) ) {
if ( get = = - EINVAL )
continue ;
return get ;
}
} while ( get = = 0 ) ;
chan - > dma . cur = 0 ;
chan - > dma . put = 0 ;
}
chan - > dma . free = get - chan - > dma . cur - 1 ;
}
return 0 ;
}
2009-12-11 19:24:15 +10:00
int
2010-02-11 16:37:26 +10:00
nouveau_dma_wait ( struct nouveau_channel * chan , int slots , int size )
2009-12-11 19:24:15 +10:00
{
2011-11-19 11:57:52 +01:00
uint64_t prev_get = 0 ;
int cnt = 0 , get ;
2009-12-11 19:24:15 +10:00
2010-02-11 16:37:26 +10:00
if ( chan - > dma . ib_max )
return nv50_dma_wait ( chan , slots , size ) ;
2009-12-11 19:24:15 +10:00
while ( chan - > dma . free < size ) {
2010-01-15 12:08:57 +10:00
get = READ_GET ( chan , & prev_get , & cnt ) ;
if ( unlikely ( get = = - EBUSY ) )
return - EBUSY ;
2009-12-11 19:24:15 +10:00
/* loop until we have a usable GET pointer. the value
* we read from the GPU may be outside the main ring if
* PFIFO is processing a buffer called from the main ring ,
* discard these values until something sensible is seen .
*
* the other case we discard GET is while the GPU is fetching
* from the SKIPS area , so the code below doesn ' t have to deal
* with some fun corner cases .
*/
2010-01-15 12:08:57 +10:00
if ( unlikely ( get = = - EINVAL ) | | get < NOUVEAU_DMA_SKIPS )
2009-12-11 19:24:15 +10:00
continue ;
if ( get < = chan - > dma . cur ) {
/* engine is fetching behind us, or is completely
* idle ( GET = = PUT ) so we have free space up until
* the end of the push buffer
*
* we can only hit that path once per call due to
* looping back to the beginning of the push buffer ,
* we ' ll hit the fetching - ahead - of - us path from that
* point on .
*
* the * one * exception to that rule is if we read
* GET = = PUT , in which case the below conditional will
* always succeed and break us out of the wait loop .
*/
chan - > dma . free = chan - > dma . max - chan - > dma . cur ;
if ( chan - > dma . free > = size )
break ;
/* not enough space left at the end of the push buffer,
* instruct the GPU to jump back to the start right
* after processing the currently pending commands .
*/
2012-07-20 08:17:34 +10:00
OUT_RING ( chan , chan - > push . vma . offset | 0x20000000 ) ;
2010-01-15 12:08:57 +10:00
/* wait for GET to depart from the skips area.
* prevents writing GET = = PUT and causing a race
* condition that causes us to think the GPU is
* idle when it ' s not .
*/
do {
get = READ_GET ( chan , & prev_get , & cnt ) ;
if ( unlikely ( get = = - EBUSY ) )
return - EBUSY ;
if ( unlikely ( get = = - EINVAL ) )
continue ;
} while ( get < = NOUVEAU_DMA_SKIPS ) ;
2009-12-11 19:24:15 +10:00
WRITE_PUT ( NOUVEAU_DMA_SKIPS ) ;
/* we're now submitting commands at the start of
* the push buffer .
*/
chan - > dma . cur =
chan - > dma . put = NOUVEAU_DMA_SKIPS ;
}
/* engine fetching ahead of us, we have space up until the
* current GET pointer . the " - 1 " is to ensure there ' s
* space left to emit a jump back to the beginning of the
* push buffer if we require it . we can never get GET = = PUT
* here , so this is safe .
*/
chan - > dma . free = get - chan - > dma . cur - 1 ;
}
return 0 ;
}