2018-07-26 17:59:13 -04:00
/*
* Copyright 2018 Red Hat Inc .
*
* 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 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 HOLDER ( S ) OR AUTHOR ( S ) 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 .
*/
# include "nouveau_dmem.h"
# include "nouveau_drv.h"
# include "nouveau_chan.h"
# include "nouveau_dma.h"
# include "nouveau_mem.h"
# include "nouveau_bo.h"
# include <nvif/class.h>
# include <nvif/object.h>
# include <nvif/if500b.h>
# include <nvif/if900b.h>
# include <linux/sched/mm.h>
# include <linux/hmm.h>
/*
* FIXME : this is ugly right now we are using TTM to allocate vram and we pin
* it in vram while in use . We likely want to overhaul memory management for
* nouveau to be more page like ( not necessarily with system page size but a
* bigger page size ) at lowest level and have some shim layer on top that would
* provide the same functionality as TTM .
*/
# define DMEM_CHUNK_SIZE (2UL << 20)
# define DMEM_CHUNK_NPAGES (DMEM_CHUNK_SIZE >> PAGE_SHIFT)
struct nouveau_migrate ;
2019-02-15 10:35:05 +10:00
enum nouveau_aper {
NOUVEAU_APER_VIRT ,
NOUVEAU_APER_VRAM ,
NOUVEAU_APER_HOST ,
} ;
2018-07-26 17:59:13 -04:00
typedef int ( * nouveau_migrate_copy_t ) ( struct nouveau_drm * drm , u64 npages ,
2019-02-15 10:35:05 +10:00
enum nouveau_aper , u64 dst_addr ,
enum nouveau_aper , u64 src_addr ) ;
2018-07-26 17:59:13 -04:00
struct nouveau_dmem_chunk {
struct list_head list ;
struct nouveau_bo * bo ;
struct nouveau_drm * drm ;
unsigned long pfn_first ;
unsigned long callocated ;
unsigned long bitmap [ BITS_TO_LONGS ( DMEM_CHUNK_NPAGES ) ] ;
spinlock_t lock ;
} ;
struct nouveau_dmem_migrate {
nouveau_migrate_copy_t copy_func ;
struct nouveau_channel * chan ;
} ;
struct nouveau_dmem {
2019-06-26 14:27:18 +02:00
struct nouveau_drm * drm ;
struct dev_pagemap pagemap ;
2018-07-26 17:59:13 -04:00
struct nouveau_dmem_migrate migrate ;
struct list_head chunk_free ;
struct list_head chunk_full ;
struct list_head chunk_empty ;
struct mutex mutex ;
} ;
2019-06-26 14:27:18 +02:00
static inline struct nouveau_dmem * page_to_dmem ( struct page * page )
{
return container_of ( page - > pgmap , struct nouveau_dmem , pagemap ) ;
}
2018-07-26 17:59:13 -04:00
struct nouveau_dmem_fault {
struct nouveau_drm * drm ;
struct nouveau_fence * fence ;
2019-02-15 15:50:16 +10:00
dma_addr_t * dma ;
unsigned long npages ;
2018-07-26 17:59:13 -04:00
} ;
struct nouveau_migrate {
struct vm_area_struct * vma ;
struct nouveau_drm * drm ;
struct nouveau_fence * fence ;
unsigned long npages ;
2019-02-15 15:50:16 +10:00
dma_addr_t * dma ;
unsigned long dma_nr ;
2018-07-26 17:59:13 -04:00
} ;
2019-06-26 14:27:18 +02:00
static void nouveau_dmem_page_free ( struct page * page )
2018-07-26 17:59:13 -04:00
{
2019-06-26 14:27:21 +02:00
struct nouveau_dmem_chunk * chunk = page - > zone_device_data ;
unsigned long idx = page_to_pfn ( page ) - chunk - > pfn_first ;
2018-07-26 17:59:13 -04:00
/*
* FIXME :
*
* This is really a bad example , we need to overhaul nouveau memory
* management to be more page focus and allow lighter locking scheme
* to be use in the process .
*/
spin_lock ( & chunk - > lock ) ;
clear_bit ( idx , chunk - > bitmap ) ;
WARN_ON ( ! chunk - > callocated ) ;
chunk - > callocated - - ;
/*
* FIXME when chunk - > callocated reach 0 we should add the chunk to
* a reclaim list so that it can be freed in case of memory pressure .
*/
spin_unlock ( & chunk - > lock ) ;
}
static void
nouveau_dmem_fault_alloc_and_copy ( struct vm_area_struct * vma ,
const unsigned long * src_pfns ,
unsigned long * dst_pfns ,
unsigned long start ,
unsigned long end ,
void * private )
{
struct nouveau_dmem_fault * fault = private ;
struct nouveau_drm * drm = fault - > drm ;
2019-02-15 15:50:16 +10:00
struct device * dev = drm - > dev - > dev ;
unsigned long addr , i , npages = 0 ;
2018-07-26 17:59:13 -04:00
nouveau_migrate_copy_t copy ;
int ret ;
/* First allocate new memory */
for ( addr = start , i = 0 ; addr < end ; addr + = PAGE_SIZE , i + + ) {
struct page * dpage , * spage ;
dst_pfns [ i ] = 0 ;
spage = migrate_pfn_to_page ( src_pfns [ i ] ) ;
if ( ! spage | | ! ( src_pfns [ i ] & MIGRATE_PFN_MIGRATE ) )
continue ;
2019-06-26 14:27:17 +02:00
dpage = alloc_page_vma ( GFP_HIGHUSER , vma , addr ) ;
2018-07-26 17:59:13 -04:00
if ( ! dpage ) {
dst_pfns [ i ] = MIGRATE_PFN_ERROR ;
continue ;
}
2019-06-26 14:27:17 +02:00
lock_page ( dpage ) ;
2018-07-26 17:59:13 -04:00
dst_pfns [ i ] = migrate_pfn ( page_to_pfn ( dpage ) ) |
MIGRATE_PFN_LOCKED ;
npages + + ;
}
2019-02-15 15:50:16 +10:00
/* Allocate storage for DMA addresses, so we can unmap later. */
fault - > dma = kmalloc ( sizeof ( * fault - > dma ) * npages , GFP_KERNEL ) ;
if ( ! fault - > dma )
2018-07-26 17:59:13 -04:00
goto error ;
/* Copy things over */
copy = drm - > dmem - > migrate . copy_func ;
2019-02-15 15:50:16 +10:00
for ( addr = start , i = 0 ; addr < end ; addr + = PAGE_SIZE , i + + ) {
2018-07-26 17:59:13 -04:00
struct nouveau_dmem_chunk * chunk ;
struct page * spage , * dpage ;
u64 src_addr , dst_addr ;
dpage = migrate_pfn_to_page ( dst_pfns [ i ] ) ;
if ( ! dpage | | dst_pfns [ i ] = = MIGRATE_PFN_ERROR )
continue ;
spage = migrate_pfn_to_page ( src_pfns [ i ] ) ;
if ( ! spage | | ! ( src_pfns [ i ] & MIGRATE_PFN_MIGRATE ) ) {
dst_pfns [ i ] = MIGRATE_PFN_ERROR ;
__free_page ( dpage ) ;
continue ;
}
2019-02-15 15:50:16 +10:00
fault - > dma [ fault - > npages ] =
dma_map_page_attrs ( dev , dpage , 0 , PAGE_SIZE ,
PCI_DMA_BIDIRECTIONAL ,
DMA_ATTR_SKIP_CPU_SYNC ) ;
if ( dma_mapping_error ( dev , fault - > dma [ fault - > npages ] ) ) {
dst_pfns [ i ] = MIGRATE_PFN_ERROR ;
__free_page ( dpage ) ;
continue ;
}
dst_addr = fault - > dma [ fault - > npages + + ] ;
2019-06-26 14:27:21 +02:00
chunk = spage - > zone_device_data ;
2018-07-26 17:59:13 -04:00
src_addr = page_to_pfn ( spage ) - chunk - > pfn_first ;
2019-02-15 14:45:57 +10:00
src_addr = ( src_addr < < PAGE_SHIFT ) + chunk - > bo - > bo . offset ;
2018-07-26 17:59:13 -04:00
2019-02-15 15:50:16 +10:00
ret = copy ( drm , 1 , NOUVEAU_APER_HOST , dst_addr ,
2019-02-15 14:45:57 +10:00
NOUVEAU_APER_VRAM , src_addr ) ;
2018-07-26 17:59:13 -04:00
if ( ret ) {
dst_pfns [ i ] = MIGRATE_PFN_ERROR ;
__free_page ( dpage ) ;
continue ;
}
}
nouveau_fence_new ( drm - > dmem - > migrate . chan , false , & fault - > fence ) ;
return ;
error :
for ( addr = start , i = 0 ; addr < end ; addr + = PAGE_SIZE , + + i ) {
struct page * page ;
if ( ! dst_pfns [ i ] | | dst_pfns [ i ] = = MIGRATE_PFN_ERROR )
continue ;
page = migrate_pfn_to_page ( dst_pfns [ i ] ) ;
dst_pfns [ i ] = MIGRATE_PFN_ERROR ;
if ( page = = NULL )
continue ;
__free_page ( page ) ;
}
}
void nouveau_dmem_fault_finalize_and_map ( struct vm_area_struct * vma ,
const unsigned long * src_pfns ,
const unsigned long * dst_pfns ,
unsigned long start ,
unsigned long end ,
void * private )
{
struct nouveau_dmem_fault * fault = private ;
struct nouveau_drm * drm = fault - > drm ;
if ( fault - > fence ) {
nouveau_fence_wait ( fault - > fence , true , false ) ;
nouveau_fence_unref ( & fault - > fence ) ;
} else {
/*
* FIXME wait for channel to be IDLE before calling finalizing
* the hmem object below ( nouveau_migrate_hmem_fini ( ) ) .
*/
}
2019-02-15 15:50:16 +10:00
while ( fault - > npages - - ) {
dma_unmap_page ( drm - > dev - > dev , fault - > dma [ fault - > npages ] ,
PAGE_SIZE , PCI_DMA_BIDIRECTIONAL ) ;
}
kfree ( fault - > dma ) ;
2018-07-26 17:59:13 -04:00
}
static const struct migrate_vma_ops nouveau_dmem_fault_migrate_ops = {
. alloc_and_copy = nouveau_dmem_fault_alloc_and_copy ,
. finalize_and_map = nouveau_dmem_fault_finalize_and_map ,
} ;
2019-06-26 14:27:18 +02:00
static vm_fault_t nouveau_dmem_migrate_to_ram ( struct vm_fault * vmf )
2018-07-26 17:59:13 -04:00
{
2019-06-26 14:27:18 +02:00
struct nouveau_dmem * dmem = page_to_dmem ( vmf - > page ) ;
2018-07-26 17:59:13 -04:00
unsigned long src [ 1 ] = { 0 } , dst [ 1 ] = { 0 } ;
2019-06-26 14:27:18 +02:00
struct nouveau_dmem_fault fault = { . drm = dmem - > drm } ;
2018-07-26 17:59:13 -04:00
int ret ;
/*
* FIXME what we really want is to find some heuristic to migrate more
* than just one page on CPU fault . When such fault happens it is very
* likely that more surrounding page will CPU fault too .
*/
2019-06-26 14:27:18 +02:00
ret = migrate_vma ( & nouveau_dmem_fault_migrate_ops , vmf - > vma ,
vmf - > address , vmf - > address + PAGE_SIZE ,
src , dst , & fault ) ;
2018-07-26 17:59:13 -04:00
if ( ret )
return VM_FAULT_SIGBUS ;
if ( dst [ 0 ] = = MIGRATE_PFN_ERROR )
return VM_FAULT_SIGBUS ;
return 0 ;
}
2019-06-26 14:27:18 +02:00
static const struct dev_pagemap_ops nouveau_dmem_pagemap_ops = {
. page_free = nouveau_dmem_page_free ,
. migrate_to_ram = nouveau_dmem_migrate_to_ram ,
2018-07-26 17:59:13 -04:00
} ;
static int
nouveau_dmem_chunk_alloc ( struct nouveau_drm * drm )
{
struct nouveau_dmem_chunk * chunk ;
int ret ;
if ( drm - > dmem = = NULL )
return - EINVAL ;
mutex_lock ( & drm - > dmem - > mutex ) ;
chunk = list_first_entry_or_null ( & drm - > dmem - > chunk_empty ,
struct nouveau_dmem_chunk ,
list ) ;
if ( chunk = = NULL ) {
mutex_unlock ( & drm - > dmem - > mutex ) ;
return - ENOMEM ;
}
list_del ( & chunk - > list ) ;
mutex_unlock ( & drm - > dmem - > mutex ) ;
ret = nouveau_bo_new ( & drm - > client , DMEM_CHUNK_SIZE , 0 ,
TTM_PL_FLAG_VRAM , 0 , 0 , NULL , NULL ,
& chunk - > bo ) ;
if ( ret )
goto out ;
ret = nouveau_bo_pin ( chunk - > bo , TTM_PL_FLAG_VRAM , false ) ;
if ( ret ) {
nouveau_bo_ref ( NULL , & chunk - > bo ) ;
goto out ;
}
bitmap_zero ( chunk - > bitmap , DMEM_CHUNK_NPAGES ) ;
spin_lock_init ( & chunk - > lock ) ;
out :
mutex_lock ( & drm - > dmem - > mutex ) ;
if ( chunk - > bo )
list_add ( & chunk - > list , & drm - > dmem - > chunk_empty ) ;
else
list_add_tail ( & chunk - > list , & drm - > dmem - > chunk_empty ) ;
mutex_unlock ( & drm - > dmem - > mutex ) ;
return ret ;
}
static struct nouveau_dmem_chunk *
nouveau_dmem_chunk_first_free_locked ( struct nouveau_drm * drm )
{
struct nouveau_dmem_chunk * chunk ;
chunk = list_first_entry_or_null ( & drm - > dmem - > chunk_free ,
struct nouveau_dmem_chunk ,
list ) ;
if ( chunk )
return chunk ;
chunk = list_first_entry_or_null ( & drm - > dmem - > chunk_empty ,
struct nouveau_dmem_chunk ,
list ) ;
if ( chunk - > bo )
return chunk ;
return NULL ;
}
static int
nouveau_dmem_pages_alloc ( struct nouveau_drm * drm ,
unsigned long npages ,
unsigned long * pages )
{
struct nouveau_dmem_chunk * chunk ;
unsigned long c ;
int ret ;
memset ( pages , 0xff , npages * sizeof ( * pages ) ) ;
mutex_lock ( & drm - > dmem - > mutex ) ;
for ( c = 0 ; c < npages ; ) {
unsigned long i ;
chunk = nouveau_dmem_chunk_first_free_locked ( drm ) ;
if ( chunk = = NULL ) {
mutex_unlock ( & drm - > dmem - > mutex ) ;
ret = nouveau_dmem_chunk_alloc ( drm ) ;
if ( ret ) {
if ( c )
2019-06-14 13:20:03 -07:00
return 0 ;
2018-07-26 17:59:13 -04:00
return ret ;
}
2019-06-14 13:20:03 -07:00
mutex_lock ( & drm - > dmem - > mutex ) ;
2018-07-26 17:59:13 -04:00
continue ;
}
spin_lock ( & chunk - > lock ) ;
i = find_first_zero_bit ( chunk - > bitmap , DMEM_CHUNK_NPAGES ) ;
while ( i < DMEM_CHUNK_NPAGES & & c < npages ) {
pages [ c ] = chunk - > pfn_first + i ;
set_bit ( i , chunk - > bitmap ) ;
chunk - > callocated + + ;
c + + ;
i = find_next_zero_bit ( chunk - > bitmap ,
DMEM_CHUNK_NPAGES , i ) ;
}
spin_unlock ( & chunk - > lock ) ;
}
mutex_unlock ( & drm - > dmem - > mutex ) ;
return 0 ;
}
static struct page *
nouveau_dmem_page_alloc_locked ( struct nouveau_drm * drm )
{
unsigned long pfns [ 1 ] ;
struct page * page ;
int ret ;
/* FIXME stop all the miss-match API ... */
ret = nouveau_dmem_pages_alloc ( drm , 1 , pfns ) ;
if ( ret )
return NULL ;
page = pfn_to_page ( pfns [ 0 ] ) ;
get_page ( page ) ;
lock_page ( page ) ;
return page ;
}
static void
nouveau_dmem_page_free_locked ( struct nouveau_drm * drm , struct page * page )
{
unlock_page ( page ) ;
put_page ( page ) ;
}
void
nouveau_dmem_resume ( struct nouveau_drm * drm )
{
struct nouveau_dmem_chunk * chunk ;
int ret ;
if ( drm - > dmem = = NULL )
return ;
mutex_lock ( & drm - > dmem - > mutex ) ;
list_for_each_entry ( chunk , & drm - > dmem - > chunk_free , list ) {
ret = nouveau_bo_pin ( chunk - > bo , TTM_PL_FLAG_VRAM , false ) ;
/* FIXME handle pin failure */
WARN_ON ( ret ) ;
}
list_for_each_entry ( chunk , & drm - > dmem - > chunk_full , list ) {
ret = nouveau_bo_pin ( chunk - > bo , TTM_PL_FLAG_VRAM , false ) ;
/* FIXME handle pin failure */
WARN_ON ( ret ) ;
}
mutex_unlock ( & drm - > dmem - > mutex ) ;
}
void
nouveau_dmem_suspend ( struct nouveau_drm * drm )
{
struct nouveau_dmem_chunk * chunk ;
if ( drm - > dmem = = NULL )
return ;
mutex_lock ( & drm - > dmem - > mutex ) ;
list_for_each_entry ( chunk , & drm - > dmem - > chunk_free , list ) {
nouveau_bo_unpin ( chunk - > bo ) ;
}
list_for_each_entry ( chunk , & drm - > dmem - > chunk_full , list ) {
nouveau_bo_unpin ( chunk - > bo ) ;
}
mutex_unlock ( & drm - > dmem - > mutex ) ;
}
void
nouveau_dmem_fini ( struct nouveau_drm * drm )
{
struct nouveau_dmem_chunk * chunk , * tmp ;
if ( drm - > dmem = = NULL )
return ;
mutex_lock ( & drm - > dmem - > mutex ) ;
WARN_ON ( ! list_empty ( & drm - > dmem - > chunk_free ) ) ;
WARN_ON ( ! list_empty ( & drm - > dmem - > chunk_full ) ) ;
list_for_each_entry_safe ( chunk , tmp , & drm - > dmem - > chunk_empty , list ) {
if ( chunk - > bo ) {
nouveau_bo_unpin ( chunk - > bo ) ;
nouveau_bo_ref ( NULL , & chunk - > bo ) ;
}
list_del ( & chunk - > list ) ;
kfree ( chunk ) ;
}
mutex_unlock ( & drm - > dmem - > mutex ) ;
}
static int
nvc0b5_migrate_copy ( struct nouveau_drm * drm , u64 npages ,
2019-02-15 10:35:05 +10:00
enum nouveau_aper dst_aper , u64 dst_addr ,
enum nouveau_aper src_aper , u64 src_addr )
2018-07-26 17:59:13 -04:00
{
struct nouveau_channel * chan = drm - > dmem - > migrate . chan ;
2019-02-15 10:35:05 +10:00
u32 launch_dma = ( 1 < < 9 ) /* MULTI_LINE_ENABLE. */ |
( 1 < < 8 ) /* DST_MEMORY_LAYOUT_PITCH. */ |
( 1 < < 7 ) /* SRC_MEMORY_LAYOUT_PITCH. */ |
( 1 < < 2 ) /* FLUSH_ENABLE_TRUE. */ |
( 2 < < 0 ) /* DATA_TRANSFER_TYPE_NON_PIPELINED. */ ;
2018-07-26 17:59:13 -04:00
int ret ;
2019-02-15 10:35:05 +10:00
ret = RING_SPACE ( chan , 13 ) ;
2018-07-26 17:59:13 -04:00
if ( ret )
return ret ;
2019-02-15 10:35:05 +10:00
if ( src_aper ! = NOUVEAU_APER_VIRT ) {
switch ( src_aper ) {
case NOUVEAU_APER_VRAM :
BEGIN_IMC0 ( chan , NvSubCopy , 0x0260 , 0 ) ;
break ;
case NOUVEAU_APER_HOST :
BEGIN_IMC0 ( chan , NvSubCopy , 0x0260 , 1 ) ;
break ;
default :
return - EINVAL ;
}
launch_dma | = 0x00001000 ; /* SRC_TYPE_PHYSICAL. */
}
if ( dst_aper ! = NOUVEAU_APER_VIRT ) {
switch ( dst_aper ) {
case NOUVEAU_APER_VRAM :
BEGIN_IMC0 ( chan , NvSubCopy , 0x0264 , 0 ) ;
break ;
case NOUVEAU_APER_HOST :
BEGIN_IMC0 ( chan , NvSubCopy , 0x0264 , 1 ) ;
break ;
default :
return - EINVAL ;
}
launch_dma | = 0x00002000 ; /* DST_TYPE_PHYSICAL. */
}
2018-07-26 17:59:13 -04:00
BEGIN_NVC0 ( chan , NvSubCopy , 0x0400 , 8 ) ;
OUT_RING ( chan , upper_32_bits ( src_addr ) ) ;
OUT_RING ( chan , lower_32_bits ( src_addr ) ) ;
OUT_RING ( chan , upper_32_bits ( dst_addr ) ) ;
OUT_RING ( chan , lower_32_bits ( dst_addr ) ) ;
OUT_RING ( chan , PAGE_SIZE ) ;
OUT_RING ( chan , PAGE_SIZE ) ;
OUT_RING ( chan , PAGE_SIZE ) ;
OUT_RING ( chan , npages ) ;
2019-02-15 10:35:05 +10:00
BEGIN_NVC0 ( chan , NvSubCopy , 0x0300 , 1 ) ;
OUT_RING ( chan , launch_dma ) ;
2018-07-26 17:59:13 -04:00
return 0 ;
}
static int
nouveau_dmem_migrate_init ( struct nouveau_drm * drm )
{
switch ( drm - > ttm . copy . oclass ) {
case PASCAL_DMA_COPY_A :
case PASCAL_DMA_COPY_B :
case VOLTA_DMA_COPY_A :
case TURING_DMA_COPY_A :
drm - > dmem - > migrate . copy_func = nvc0b5_migrate_copy ;
drm - > dmem - > migrate . chan = drm - > ttm . chan ;
return 0 ;
default :
break ;
}
return - ENODEV ;
}
void
nouveau_dmem_init ( struct nouveau_drm * drm )
{
struct device * device = drm - > dev - > dev ;
2019-06-26 14:27:18 +02:00
struct resource * res ;
unsigned long i , size , pfn_first ;
2018-07-26 17:59:13 -04:00
int ret ;
/* This only make sense on PASCAL or newer */
if ( drm - > client . device . info . family < NV_DEVICE_INFO_V0_PASCAL )
return ;
if ( ! ( drm - > dmem = kzalloc ( sizeof ( * drm - > dmem ) , GFP_KERNEL ) ) )
return ;
2019-06-26 14:27:18 +02:00
drm - > dmem - > drm = drm ;
2018-07-26 17:59:13 -04:00
mutex_init ( & drm - > dmem - > mutex ) ;
INIT_LIST_HEAD ( & drm - > dmem - > chunk_free ) ;
INIT_LIST_HEAD ( & drm - > dmem - > chunk_full ) ;
INIT_LIST_HEAD ( & drm - > dmem - > chunk_empty ) ;
size = ALIGN ( drm - > client . device . info . ram_user , DMEM_CHUNK_SIZE ) ;
/* Initialize migration dma helpers before registering memory */
ret = nouveau_dmem_migrate_init ( drm ) ;
2019-06-26 14:27:18 +02:00
if ( ret )
goto out_free ;
2018-07-26 17:59:13 -04:00
/*
* FIXME we need some kind of policy to decide how much VRAM we
* want to register with HMM . For now just register everything
* and latter if we want to do thing like over commit then we
* could revisit this .
*/
2019-06-26 14:27:18 +02:00
res = devm_request_free_mem_region ( device , & iomem_resource , size ) ;
if ( IS_ERR ( res ) )
goto out_free ;
drm - > dmem - > pagemap . type = MEMORY_DEVICE_PRIVATE ;
drm - > dmem - > pagemap . res = * res ;
drm - > dmem - > pagemap . ops = & nouveau_dmem_pagemap_ops ;
if ( IS_ERR ( devm_memremap_pages ( device , & drm - > dmem - > pagemap ) ) )
goto out_free ;
pfn_first = res - > start > > PAGE_SHIFT ;
2018-07-26 17:59:13 -04:00
for ( i = 0 ; i < ( size / DMEM_CHUNK_SIZE ) ; + + i ) {
struct nouveau_dmem_chunk * chunk ;
struct page * page ;
unsigned long j ;
chunk = kzalloc ( sizeof ( * chunk ) , GFP_KERNEL ) ;
if ( chunk = = NULL ) {
nouveau_dmem_fini ( drm ) ;
return ;
}
chunk - > drm = drm ;
2019-06-26 14:27:18 +02:00
chunk - > pfn_first = pfn_first + ( i * DMEM_CHUNK_NPAGES ) ;
2018-07-26 17:59:13 -04:00
list_add_tail ( & chunk - > list , & drm - > dmem - > chunk_empty ) ;
page = pfn_to_page ( chunk - > pfn_first ) ;
2019-06-26 14:27:21 +02:00
for ( j = 0 ; j < DMEM_CHUNK_NPAGES ; + + j , + + page )
page - > zone_device_data = chunk ;
2018-07-26 17:59:13 -04:00
}
NV_INFO ( drm , " DMEM: registered %ldMB of device memory \n " , size > > 20 ) ;
2019-06-26 14:27:18 +02:00
return ;
out_free :
kfree ( drm - > dmem ) ;
drm - > dmem = NULL ;
2018-07-26 17:59:13 -04:00
}
static void
nouveau_dmem_migrate_alloc_and_copy ( struct vm_area_struct * vma ,
const unsigned long * src_pfns ,
unsigned long * dst_pfns ,
unsigned long start ,
unsigned long end ,
void * private )
{
struct nouveau_migrate * migrate = private ;
struct nouveau_drm * drm = migrate - > drm ;
2019-02-15 15:50:16 +10:00
struct device * dev = drm - > dev - > dev ;
unsigned long addr , i , npages = 0 ;
2018-07-26 17:59:13 -04:00
nouveau_migrate_copy_t copy ;
int ret ;
/* First allocate new memory */
for ( addr = start , i = 0 ; addr < end ; addr + = PAGE_SIZE , i + + ) {
struct page * dpage , * spage ;
dst_pfns [ i ] = 0 ;
spage = migrate_pfn_to_page ( src_pfns [ i ] ) ;
if ( ! spage | | ! ( src_pfns [ i ] & MIGRATE_PFN_MIGRATE ) )
continue ;
dpage = nouveau_dmem_page_alloc_locked ( drm ) ;
if ( ! dpage )
continue ;
dst_pfns [ i ] = migrate_pfn ( page_to_pfn ( dpage ) ) |
MIGRATE_PFN_LOCKED |
MIGRATE_PFN_DEVICE ;
npages + + ;
}
if ( ! npages )
return ;
2019-02-15 15:50:16 +10:00
/* Allocate storage for DMA addresses, so we can unmap later. */
migrate - > dma = kmalloc ( sizeof ( * migrate - > dma ) * npages , GFP_KERNEL ) ;
if ( ! migrate - > dma )
2018-07-26 17:59:13 -04:00
goto error ;
/* Copy things over */
copy = drm - > dmem - > migrate . copy_func ;
2019-02-15 15:50:16 +10:00
for ( addr = start , i = 0 ; addr < end ; addr + = PAGE_SIZE , i + + ) {
2018-07-26 17:59:13 -04:00
struct nouveau_dmem_chunk * chunk ;
struct page * spage , * dpage ;
u64 src_addr , dst_addr ;
dpage = migrate_pfn_to_page ( dst_pfns [ i ] ) ;
if ( ! dpage | | dst_pfns [ i ] = = MIGRATE_PFN_ERROR )
continue ;
2019-06-26 14:27:21 +02:00
chunk = dpage - > zone_device_data ;
2018-07-26 17:59:13 -04:00
dst_addr = page_to_pfn ( dpage ) - chunk - > pfn_first ;
2019-02-15 14:45:57 +10:00
dst_addr = ( dst_addr < < PAGE_SHIFT ) + chunk - > bo - > bo . offset ;
2018-07-26 17:59:13 -04:00
spage = migrate_pfn_to_page ( src_pfns [ i ] ) ;
if ( ! spage | | ! ( src_pfns [ i ] & MIGRATE_PFN_MIGRATE ) ) {
nouveau_dmem_page_free_locked ( drm , dpage ) ;
dst_pfns [ i ] = 0 ;
continue ;
}
2019-02-15 15:50:16 +10:00
migrate - > dma [ migrate - > dma_nr ] =
dma_map_page_attrs ( dev , spage , 0 , PAGE_SIZE ,
PCI_DMA_BIDIRECTIONAL ,
DMA_ATTR_SKIP_CPU_SYNC ) ;
if ( dma_mapping_error ( dev , migrate - > dma [ migrate - > dma_nr ] ) ) {
nouveau_dmem_page_free_locked ( drm , dpage ) ;
dst_pfns [ i ] = 0 ;
continue ;
}
src_addr = migrate - > dma [ migrate - > dma_nr + + ] ;
2018-07-26 17:59:13 -04:00
2019-02-15 14:45:57 +10:00
ret = copy ( drm , 1 , NOUVEAU_APER_VRAM , dst_addr ,
2019-02-15 15:50:16 +10:00
NOUVEAU_APER_HOST , src_addr ) ;
2018-07-26 17:59:13 -04:00
if ( ret ) {
nouveau_dmem_page_free_locked ( drm , dpage ) ;
dst_pfns [ i ] = 0 ;
continue ;
}
}
nouveau_fence_new ( drm - > dmem - > migrate . chan , false , & migrate - > fence ) ;
return ;
error :
for ( addr = start , i = 0 ; addr < end ; addr + = PAGE_SIZE , + + i ) {
struct page * page ;
if ( ! dst_pfns [ i ] | | dst_pfns [ i ] = = MIGRATE_PFN_ERROR )
continue ;
page = migrate_pfn_to_page ( dst_pfns [ i ] ) ;
dst_pfns [ i ] = MIGRATE_PFN_ERROR ;
if ( page = = NULL )
continue ;
__free_page ( page ) ;
}
}
void nouveau_dmem_migrate_finalize_and_map ( struct vm_area_struct * vma ,
const unsigned long * src_pfns ,
const unsigned long * dst_pfns ,
unsigned long start ,
unsigned long end ,
void * private )
{
struct nouveau_migrate * migrate = private ;
struct nouveau_drm * drm = migrate - > drm ;
if ( migrate - > fence ) {
nouveau_fence_wait ( migrate - > fence , true , false ) ;
nouveau_fence_unref ( & migrate - > fence ) ;
} else {
/*
* FIXME wait for channel to be IDLE before finalizing
* the hmem object below ( nouveau_migrate_hmem_fini ( ) ) ?
*/
}
2019-02-15 15:50:16 +10:00
while ( migrate - > dma_nr - - ) {
dma_unmap_page ( drm - > dev - > dev , migrate - > dma [ migrate - > dma_nr ] ,
PAGE_SIZE , PCI_DMA_BIDIRECTIONAL ) ;
}
kfree ( migrate - > dma ) ;
2018-07-26 17:59:13 -04:00
/*
* FIXME optimization : update GPU page table to point to newly
* migrated memory .
*/
}
static const struct migrate_vma_ops nouveau_dmem_migrate_ops = {
. alloc_and_copy = nouveau_dmem_migrate_alloc_and_copy ,
. finalize_and_map = nouveau_dmem_migrate_finalize_and_map ,
} ;
int
nouveau_dmem_migrate_vma ( struct nouveau_drm * drm ,
struct vm_area_struct * vma ,
unsigned long start ,
unsigned long end )
{
unsigned long * src_pfns , * dst_pfns , npages ;
struct nouveau_migrate migrate = { 0 } ;
unsigned long i , c , max ;
int ret = 0 ;
npages = ( end - start ) > > PAGE_SHIFT ;
max = min ( SG_MAX_SINGLE_ALLOC , npages ) ;
src_pfns = kzalloc ( sizeof ( long ) * max , GFP_KERNEL ) ;
if ( src_pfns = = NULL )
return - ENOMEM ;
dst_pfns = kzalloc ( sizeof ( long ) * max , GFP_KERNEL ) ;
if ( dst_pfns = = NULL ) {
kfree ( src_pfns ) ;
return - ENOMEM ;
}
migrate . drm = drm ;
migrate . vma = vma ;
migrate . npages = npages ;
for ( i = 0 ; i < npages ; i + = c ) {
unsigned long next ;
c = min ( SG_MAX_SINGLE_ALLOC , npages ) ;
next = start + ( c < < PAGE_SHIFT ) ;
ret = migrate_vma ( & nouveau_dmem_migrate_ops , vma , start ,
next , src_pfns , dst_pfns , & migrate ) ;
if ( ret )
goto out ;
start = next ;
}
out :
kfree ( dst_pfns ) ;
kfree ( src_pfns ) ;
return ret ;
}
static inline bool
nouveau_dmem_page ( struct nouveau_drm * drm , struct page * page )
{
2019-06-26 14:27:18 +02:00
return is_device_private_page ( page ) & & drm - > dmem = = page_to_dmem ( page ) ;
2018-07-26 17:59:13 -04:00
}
void
nouveau_dmem_convert_pfn ( struct nouveau_drm * drm ,
struct hmm_range * range )
{
unsigned long i , npages ;
npages = ( range - > end - range - > start ) > > PAGE_SHIFT ;
for ( i = 0 ; i < npages ; + + i ) {
struct nouveau_dmem_chunk * chunk ;
struct page * page ;
uint64_t addr ;
page = hmm_pfn_to_page ( range , range - > pfns [ i ] ) ;
if ( page = = NULL )
continue ;
if ( ! ( range - > pfns [ i ] & range - > flags [ HMM_PFN_DEVICE_PRIVATE ] ) ) {
continue ;
}
if ( ! nouveau_dmem_page ( drm , page ) ) {
WARN ( 1 , " Some unknown device memory ! \n " ) ;
range - > pfns [ i ] = 0 ;
continue ;
}
2019-06-26 14:27:21 +02:00
chunk = page - > zone_device_data ;
2018-07-26 17:59:13 -04:00
addr = page_to_pfn ( page ) - chunk - > pfn_first ;
addr = ( addr + chunk - > bo - > bo . mem . start ) < < PAGE_SHIFT ;
range - > pfns [ i ] & = ( ( 1UL < < range - > pfn_shift ) - 1 ) ;
range - > pfns [ i ] | = ( addr > > PAGE_SHIFT ) < < range - > pfn_shift ;
}
}