2019-02-16 00:39:11 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016 - 2019 HabanaLabs , Ltd .
* All Rights Reserved .
*/
# include "habanalabs.h"
# include <linux/pci.h>
# include <linux/delay.h>
static void hpriv_release ( struct kref * ref )
{
struct hl_fpriv * hpriv ;
struct hl_device * hdev ;
hpriv = container_of ( ref , struct hl_fpriv , refcount ) ;
hdev = hpriv - > hdev ;
put_pid ( hpriv - > taskpid ) ;
kfree ( hpriv ) ;
2019-02-16 00:39:14 +02:00
/* Now the FD is really closed */
atomic_dec ( & hdev - > fd_open_cnt ) ;
/* This allows a new user context to open the device */
hdev - > user_ctx = NULL ;
2019-02-16 00:39:11 +02:00
}
void hl_hpriv_get ( struct hl_fpriv * hpriv )
{
kref_get ( & hpriv - > refcount ) ;
}
void hl_hpriv_put ( struct hl_fpriv * hpriv )
{
kref_put ( & hpriv - > refcount , hpriv_release ) ;
}
/*
* hl_device_release - release function for habanalabs device
*
* @ inode : pointer to inode structure
* @ filp : pointer to file structure
*
* Called when process closes an habanalabs device
*/
static int hl_device_release ( struct inode * inode , struct file * filp )
{
struct hl_fpriv * hpriv = filp - > private_data ;
2019-02-16 00:39:15 +02:00
hl_cb_mgr_fini ( hpriv - > hdev , & hpriv - > cb_mgr ) ;
2019-02-16 00:39:14 +02:00
hl_ctx_mgr_fini ( hpriv - > hdev , & hpriv - > ctx_mgr ) ;
2019-02-16 00:39:11 +02:00
filp - > private_data = NULL ;
hl_hpriv_put ( hpriv ) ;
return 0 ;
}
2019-02-16 00:39:15 +02:00
/*
* hl_mmap - mmap function for habanalabs device
*
* @ * filp : pointer to file structure
* @ * vma : pointer to vm_area_struct of the process
*
* Called when process does an mmap on habanalabs device . Call the device ' s mmap
* function at the end of the common code .
*/
static int hl_mmap ( struct file * filp , struct vm_area_struct * vma )
{
struct hl_fpriv * hpriv = filp - > private_data ;
if ( ( vma - > vm_pgoff & HL_MMAP_CB_MASK ) = = HL_MMAP_CB_MASK ) {
vma - > vm_pgoff ^ = HL_MMAP_CB_MASK ;
return hl_cb_mmap ( hpriv , vma ) ;
}
return hpriv - > hdev - > asic_funcs - > mmap ( hpriv , vma ) ;
}
2019-02-16 00:39:11 +02:00
static const struct file_operations hl_ops = {
. owner = THIS_MODULE ,
. open = hl_device_open ,
2019-02-16 00:39:15 +02:00
. release = hl_device_release ,
. mmap = hl_mmap ,
. unlocked_ioctl = hl_ioctl ,
. compat_ioctl = hl_ioctl
2019-02-16 00:39:11 +02:00
} ;
/*
* device_setup_cdev - setup cdev and device for habanalabs device
*
* @ hdev : pointer to habanalabs device structure
* @ hclass : pointer to the class object of the device
* @ minor : minor number of the specific device
* @ fpos : file operations to install for this device
*
* Create a cdev and a Linux device for habanalabs ' s device . Need to be
* called at the end of the habanalabs device initialization process ,
* because this function exposes the device to the user
*/
static int device_setup_cdev ( struct hl_device * hdev , struct class * hclass ,
int minor , const struct file_operations * fops )
{
int err , devno = MKDEV ( hdev - > major , minor ) ;
struct cdev * hdev_cdev = & hdev - > cdev ;
char * name ;
name = kasprintf ( GFP_KERNEL , " hl%d " , hdev - > id ) ;
if ( ! name )
return - ENOMEM ;
cdev_init ( hdev_cdev , fops ) ;
hdev_cdev - > owner = THIS_MODULE ;
err = cdev_add ( hdev_cdev , devno , 1 ) ;
if ( err ) {
pr_err ( " Failed to add char device %s \n " , name ) ;
goto err_cdev_add ;
}
hdev - > dev = device_create ( hclass , NULL , devno , NULL , " %s " , name ) ;
if ( IS_ERR ( hdev - > dev ) ) {
pr_err ( " Failed to create device %s \n " , name ) ;
err = PTR_ERR ( hdev - > dev ) ;
goto err_device_create ;
}
dev_set_drvdata ( hdev - > dev , hdev ) ;
kfree ( name ) ;
return 0 ;
err_device_create :
cdev_del ( hdev_cdev ) ;
err_cdev_add :
kfree ( name ) ;
return err ;
}
/*
* device_early_init - do some early initialization for the habanalabs device
*
* @ hdev : pointer to habanalabs device structure
*
* Install the relevant function pointers and call the early_init function ,
* if such a function exists
*/
static int device_early_init ( struct hl_device * hdev )
{
2019-02-16 00:39:13 +02:00
int rc ;
2019-02-16 00:39:11 +02:00
switch ( hdev - > asic_type ) {
case ASIC_GOYA :
2019-02-16 00:39:13 +02:00
goya_set_asic_funcs ( hdev ) ;
2019-02-16 00:39:11 +02:00
strlcpy ( hdev - > asic_name , " GOYA " , sizeof ( hdev - > asic_name ) ) ;
break ;
default :
dev_err ( hdev - > dev , " Unrecognized ASIC type %d \n " ,
hdev - > asic_type ) ;
return - EINVAL ;
}
2019-02-16 00:39:13 +02:00
rc = hdev - > asic_funcs - > early_init ( hdev ) ;
if ( rc )
return rc ;
2019-02-16 00:39:14 +02:00
rc = hl_asid_init ( hdev ) ;
if ( rc )
goto early_fini ;
2019-02-16 00:39:15 +02:00
hl_cb_mgr_init ( & hdev - > kernel_cb_mgr ) ;
2019-02-16 00:39:14 +02:00
mutex_init ( & hdev - > fd_open_cnt_lock ) ;
atomic_set ( & hdev - > fd_open_cnt , 0 ) ;
2019-02-16 00:39:11 +02:00
return 0 ;
2019-02-16 00:39:14 +02:00
early_fini :
if ( hdev - > asic_funcs - > early_fini )
hdev - > asic_funcs - > early_fini ( hdev ) ;
return rc ;
2019-02-16 00:39:11 +02:00
}
/*
* device_early_fini - finalize all that was done in device_early_init
*
* @ hdev : pointer to habanalabs device structure
*
*/
static void device_early_fini ( struct hl_device * hdev )
{
2019-02-16 00:39:13 +02:00
2019-02-16 00:39:15 +02:00
hl_cb_mgr_fini ( hdev , & hdev - > kernel_cb_mgr ) ;
2019-02-16 00:39:14 +02:00
hl_asid_fini ( hdev ) ;
2019-02-16 00:39:13 +02:00
if ( hdev - > asic_funcs - > early_fini )
hdev - > asic_funcs - > early_fini ( hdev ) ;
2019-02-16 00:39:14 +02:00
mutex_destroy ( & hdev - > fd_open_cnt_lock ) ;
2019-02-16 00:39:11 +02:00
}
/*
* hl_device_suspend - initiate device suspend
*
* @ hdev : pointer to habanalabs device structure
*
* Puts the hw in the suspend state ( all asics ) .
* Returns 0 for success or an error on failure .
* Called at driver suspend .
*/
int hl_device_suspend ( struct hl_device * hdev )
{
2019-02-16 00:39:13 +02:00
int rc ;
2019-02-16 00:39:11 +02:00
pci_save_state ( hdev - > pdev ) ;
2019-02-16 00:39:13 +02:00
rc = hdev - > asic_funcs - > suspend ( hdev ) ;
if ( rc )
dev_err ( hdev - > dev ,
" Failed to disable PCI access of device CPU \n " ) ;
2019-02-16 00:39:11 +02:00
/* Shut down the device */
pci_disable_device ( hdev - > pdev ) ;
pci_set_power_state ( hdev - > pdev , PCI_D3hot ) ;
return 0 ;
}
/*
* hl_device_resume - initiate device resume
*
* @ hdev : pointer to habanalabs device structure
*
* Bring the hw back to operating state ( all asics ) .
* Returns 0 for success or an error on failure .
* Called at driver resume .
*/
int hl_device_resume ( struct hl_device * hdev )
{
int rc ;
pci_set_power_state ( hdev - > pdev , PCI_D0 ) ;
pci_restore_state ( hdev - > pdev ) ;
rc = pci_enable_device ( hdev - > pdev ) ;
if ( rc ) {
dev_err ( hdev - > dev ,
" Failed to enable PCI device in resume \n " ) ;
return rc ;
}
2019-02-16 00:39:13 +02:00
rc = hdev - > asic_funcs - > resume ( hdev ) ;
if ( rc ) {
dev_err ( hdev - > dev ,
" Failed to enable PCI access from device CPU \n " ) ;
return rc ;
}
2019-02-16 00:39:11 +02:00
return 0 ;
}
/*
* hl_device_init - main initialization function for habanalabs device
*
* @ hdev : pointer to habanalabs device structure
*
* Allocate an id for the device , do early initialization and then call the
* ASIC specific initialization functions . Finally , create the cdev and the
* Linux device to expose it to the user
*/
int hl_device_init ( struct hl_device * hdev , struct class * hclass )
{
int rc ;
/* Create device */
rc = device_setup_cdev ( hdev , hclass , hdev - > id , & hl_ops ) ;
if ( rc )
goto out_disabled ;
/* Initialize ASIC function pointers and perform early init */
rc = device_early_init ( hdev ) ;
if ( rc )
goto release_device ;
2019-02-16 00:39:13 +02:00
/*
* Start calling ASIC initialization . First S / W then H / W and finally
* late init
*/
rc = hdev - > asic_funcs - > sw_init ( hdev ) ;
if ( rc )
goto early_fini ;
2019-02-16 00:39:14 +02:00
/* Allocate the kernel context */
hdev - > kernel_ctx = kzalloc ( sizeof ( * hdev - > kernel_ctx ) , GFP_KERNEL ) ;
if ( ! hdev - > kernel_ctx ) {
rc = - ENOMEM ;
goto sw_fini ;
}
hdev - > user_ctx = NULL ;
rc = hl_ctx_init ( hdev , hdev - > kernel_ctx , true ) ;
if ( rc ) {
dev_err ( hdev - > dev , " failed to initialize kernel context \n " ) ;
goto free_ctx ;
}
2019-02-16 00:39:15 +02:00
rc = hl_cb_pool_init ( hdev ) ;
if ( rc ) {
dev_err ( hdev - > dev , " failed to initialize CB pool \n " ) ;
goto release_ctx ;
}
2019-02-16 00:39:16 +02:00
rc = hdev - > asic_funcs - > hw_init ( hdev ) ;
if ( rc ) {
dev_err ( hdev - > dev , " failed to initialize the H/W \n " ) ;
rc = 0 ;
goto out_disabled ;
}
hdev - > disabled = false ;
2019-02-16 00:39:11 +02:00
dev_notice ( hdev - > dev ,
" Successfully added device to habanalabs driver \n " ) ;
return 0 ;
2019-02-16 00:39:15 +02:00
release_ctx :
if ( hl_ctx_put ( hdev - > kernel_ctx ) ! = 1 )
dev_err ( hdev - > dev ,
" kernel ctx is still alive on initialization failure \n " ) ;
2019-02-16 00:39:14 +02:00
free_ctx :
kfree ( hdev - > kernel_ctx ) ;
sw_fini :
hdev - > asic_funcs - > sw_fini ( hdev ) ;
2019-02-16 00:39:13 +02:00
early_fini :
device_early_fini ( hdev ) ;
2019-02-16 00:39:11 +02:00
release_device :
device_destroy ( hclass , hdev - > dev - > devt ) ;
cdev_del ( & hdev - > cdev ) ;
out_disabled :
hdev - > disabled = true ;
if ( hdev - > pdev )
dev_err ( & hdev - > pdev - > dev ,
" Failed to initialize hl%d. Device is NOT usable ! \n " ,
hdev - > id ) ;
else
pr_err ( " Failed to initialize hl%d. Device is NOT usable ! \n " ,
hdev - > id ) ;
return rc ;
}
/*
* hl_device_fini - main tear - down function for habanalabs device
*
* @ hdev : pointer to habanalabs device structure
*
* Destroy the device , call ASIC fini functions and release the id
*/
void hl_device_fini ( struct hl_device * hdev )
{
dev_info ( hdev - > dev , " Removing device \n " ) ;
/* Mark device as disabled */
hdev - > disabled = true ;
2019-02-16 00:39:15 +02:00
hl_cb_pool_fini ( hdev ) ;
2019-02-16 00:39:14 +02:00
/* Release kernel context */
if ( ( hdev - > kernel_ctx ) & & ( hl_ctx_put ( hdev - > kernel_ctx ) ! = 1 ) )
dev_err ( hdev - > dev , " kernel ctx is still alive \n " ) ;
2019-02-16 00:39:16 +02:00
/* Reset the H/W. It will be in idle state after this returns */
hdev - > asic_funcs - > hw_fini ( hdev , true ) ;
2019-02-16 00:39:13 +02:00
/* Call ASIC S/W finalize function */
hdev - > asic_funcs - > sw_fini ( hdev ) ;
2019-02-16 00:39:11 +02:00
device_early_fini ( hdev ) ;
/* Hide device from user */
device_destroy ( hdev - > dev - > class , hdev - > dev - > devt ) ;
cdev_del ( & hdev - > cdev ) ;
pr_info ( " removed device successfully \n " ) ;
}
/*
* hl_poll_timeout_memory - Periodically poll a host memory address
* until it is not zero or a timeout occurs
* @ hdev : pointer to habanalabs device structure
* @ addr : Address to poll
* @ timeout_us : timeout in us
* @ val : Variable to read the value into
*
* Returns 0 on success and - ETIMEDOUT upon a timeout . In either
* case , the last read value at @ addr is stored in @ val . Must not
* be called from atomic context if sleep_us or timeout_us are used .
*
* The function sleeps for 100u s with timeout value of
* timeout_us
*/
int hl_poll_timeout_memory ( struct hl_device * hdev , u64 addr ,
u32 timeout_us , u32 * val )
{
/*
* address in this function points always to a memory location in the
* host ' s ( server ' s ) memory . That location is updated asynchronously
* either by the direct access of the device or by another core
*/
u32 * paddr = ( u32 * ) ( uintptr_t ) addr ;
ktime_t timeout = ktime_add_us ( ktime_get ( ) , timeout_us ) ;
might_sleep ( ) ;
for ( ; ; ) {
/*
* Flush CPU read / write buffers to make sure we read updates
* done by other cores or by the device
*/
mb ( ) ;
* val = * paddr ;
if ( * val )
break ;
if ( ktime_compare ( ktime_get ( ) , timeout ) > 0 ) {
* val = * paddr ;
break ;
}
usleep_range ( ( 100 > > 2 ) + 1 , 100 ) ;
}
return * val ? 0 : - ETIMEDOUT ;
}
/*
* hl_poll_timeout_devicememory - Periodically poll a device memory address
* until it is not zero or a timeout occurs
* @ hdev : pointer to habanalabs device structure
* @ addr : Device address to poll
* @ timeout_us : timeout in us
* @ val : Variable to read the value into
*
* Returns 0 on success and - ETIMEDOUT upon a timeout . In either
* case , the last read value at @ addr is stored in @ val . Must not
* be called from atomic context if sleep_us or timeout_us are used .
*
* The function sleeps for 100u s with timeout value of
* timeout_us
*/
int hl_poll_timeout_device_memory ( struct hl_device * hdev , void __iomem * addr ,
u32 timeout_us , u32 * val )
{
ktime_t timeout = ktime_add_us ( ktime_get ( ) , timeout_us ) ;
might_sleep ( ) ;
for ( ; ; ) {
* val = readl ( addr ) ;
if ( * val )
break ;
if ( ktime_compare ( ktime_get ( ) , timeout ) > 0 ) {
* val = readl ( addr ) ;
break ;
}
usleep_range ( ( 100 > > 2 ) + 1 , 100 ) ;
}
return * val ? 0 : - ETIMEDOUT ;
}
2019-02-16 00:39:13 +02:00
/*
* MMIO register access helper functions .
*/
/*
* hl_rreg - Read an MMIO register
*
* @ hdev : pointer to habanalabs device structure
* @ reg : MMIO register offset ( in bytes )
*
* Returns the value of the MMIO register we are asked to read
*
*/
inline u32 hl_rreg ( struct hl_device * hdev , u32 reg )
{
return readl ( hdev - > rmmio + reg ) ;
}
/*
* hl_wreg - Write to an MMIO register
*
* @ hdev : pointer to habanalabs device structure
* @ reg : MMIO register offset ( in bytes )
* @ val : 32 - bit value
*
* Writes the 32 - bit value into the MMIO register
*
*/
inline void hl_wreg ( struct hl_device * hdev , u32 reg , u32 val )
{
writel ( val , hdev - > rmmio + reg ) ;
}