2016-11-01 14:14:29 -05:00
/*
* FPGA Region - Device Tree support for FPGA programming under Linux
*
* Copyright ( C ) 2013 - 2016 Altera 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/fpga/fpga-bridge.h>
# include <linux/fpga/fpga-mgr.h>
# include <linux/idr.h>
# include <linux/kernel.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/of_platform.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
/**
* struct fpga_region - FPGA Region structure
* @ dev : FPGA Region device
* @ mutex : enforces exclusive reference to region
* @ bridge_list : list of FPGA bridges specified in region
* @ info : fpga image specific information
*/
struct fpga_region {
struct device dev ;
struct mutex mutex ; /* for exclusive reference to region */
struct list_head bridge_list ;
struct fpga_image_info * info ;
} ;
# define to_fpga_region(d) container_of(d, struct fpga_region, dev)
static DEFINE_IDA ( fpga_region_ida ) ;
static struct class * fpga_region_class ;
static const struct of_device_id fpga_region_of_match [ ] = {
{ . compatible = " fpga-region " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , fpga_region_of_match ) ;
static int fpga_region_of_node_match ( struct device * dev , const void * data )
{
return dev - > of_node = = data ;
}
/**
* fpga_region_find - find FPGA region
* @ np : device node of FPGA Region
* Caller will need to put_device ( & region - > dev ) when done .
* Returns FPGA Region struct or NULL
*/
static struct fpga_region * fpga_region_find ( struct device_node * np )
{
struct device * dev ;
dev = class_find_device ( fpga_region_class , NULL , np ,
fpga_region_of_node_match ) ;
if ( ! dev )
return NULL ;
return to_fpga_region ( dev ) ;
}
/**
* fpga_region_get - get an exclusive reference to a fpga region
* @ region : FPGA Region struct
*
* Caller should call fpga_region_put ( ) when done with region .
*
* Return fpga_region struct if successful .
* Return - EBUSY if someone already has a reference to the region .
* Return - ENODEV if @ np is not a FPGA Region .
*/
static struct fpga_region * fpga_region_get ( struct fpga_region * region )
{
struct device * dev = & region - > dev ;
if ( ! mutex_trylock ( & region - > mutex ) ) {
dev_dbg ( dev , " %s: FPGA Region already in use \n " , __func__ ) ;
return ERR_PTR ( - EBUSY ) ;
}
get_device ( dev ) ;
of_node_get ( dev - > of_node ) ;
if ( ! try_module_get ( dev - > parent - > driver - > owner ) ) {
of_node_put ( dev - > of_node ) ;
put_device ( dev ) ;
mutex_unlock ( & region - > mutex ) ;
return ERR_PTR ( - ENODEV ) ;
}
dev_dbg ( & region - > dev , " get \n " ) ;
return region ;
}
/**
* fpga_region_put - release a reference to a region
*
* @ region : FPGA region
*/
static void fpga_region_put ( struct fpga_region * region )
{
struct device * dev = & region - > dev ;
dev_dbg ( & region - > dev , " put \n " ) ;
module_put ( dev - > parent - > driver - > owner ) ;
of_node_put ( dev - > of_node ) ;
put_device ( dev ) ;
mutex_unlock ( & region - > mutex ) ;
}
/**
* fpga_region_get_manager - get exclusive reference for FPGA manager
* @ region : FPGA region
*
* Get FPGA Manager from " fpga-mgr " property or from ancestor region .
*
* Caller should call fpga_mgr_put ( ) when done with manager .
*
* Return : fpga manager struct or IS_ERR ( ) condition containing error code .
*/
static struct fpga_manager * fpga_region_get_manager ( struct fpga_region * region )
{
struct device * dev = & region - > dev ;
struct device_node * np = dev - > of_node ;
struct device_node * mgr_node ;
struct fpga_manager * mgr ;
of_node_get ( np ) ;
while ( np ) {
if ( of_device_is_compatible ( np , " fpga-region " ) ) {
mgr_node = of_parse_phandle ( np , " fpga-mgr " , 0 ) ;
if ( mgr_node ) {
mgr = of_fpga_mgr_get ( mgr_node ) ;
of_node_put ( np ) ;
return mgr ;
}
}
np = of_get_next_parent ( np ) ;
}
of_node_put ( np ) ;
return ERR_PTR ( - EINVAL ) ;
}
/**
* fpga_region_get_bridges - create a list of bridges
* @ region : FPGA region
* @ overlay : device node of the overlay
*
* Create a list of bridges including the parent bridge and the bridges
* specified by " fpga-bridges " property . Note that the
* fpga_bridges_enable / disable / put functions are all fine with an empty list
* if that happens .
*
* Caller should call fpga_bridges_put ( & region - > bridge_list ) when
* done with the bridges .
*
* Return 0 for success ( even if there are no bridges specified )
* or - EBUSY if any of the bridges are in use .
*/
static int fpga_region_get_bridges ( struct fpga_region * region ,
struct device_node * overlay )
{
struct device * dev = & region - > dev ;
struct device_node * region_np = dev - > of_node ;
struct device_node * br , * np , * parent_br = NULL ;
int i , ret ;
/* If parent is a bridge, add to list */
ret = fpga_bridge_get_to_list ( region_np - > parent , region - > info ,
& region - > bridge_list ) ;
if ( ret = = - EBUSY )
return ret ;
if ( ! ret )
parent_br = region_np - > parent ;
/* If overlay has a list of bridges, use it. */
if ( of_parse_phandle ( overlay , " fpga-bridges " , 0 ) )
np = overlay ;
else
np = region_np ;
for ( i = 0 ; ; i + + ) {
br = of_parse_phandle ( np , " fpga-bridges " , i ) ;
if ( ! br )
break ;
/* If parent bridge is in list, skip it. */
if ( br = = parent_br )
continue ;
/* If node is a bridge, get it and add to list */
ret = fpga_bridge_get_to_list ( br , region - > info ,
& region - > bridge_list ) ;
/* If any of the bridges are in use, give up */
if ( ret = = - EBUSY ) {
fpga_bridges_put ( & region - > bridge_list ) ;
return - EBUSY ;
}
}
return 0 ;
}
/**
* fpga_region_program_fpga - program FPGA
* @ region : FPGA region
* @ firmware_name : name of FPGA image firmware file
* @ overlay : device node of the overlay
* Program an FPGA using information in the device tree .
* Function assumes that there is a firmware - name property .
* Return 0 for success or negative error code .
*/
static int fpga_region_program_fpga ( struct fpga_region * region ,
const char * firmware_name ,
struct device_node * overlay )
{
struct fpga_manager * mgr ;
int ret ;
region = fpga_region_get ( region ) ;
if ( IS_ERR ( region ) ) {
pr_err ( " failed to get fpga region \n " ) ;
return PTR_ERR ( region ) ;
}
mgr = fpga_region_get_manager ( region ) ;
if ( IS_ERR ( mgr ) ) {
pr_err ( " failed to get fpga region manager \n " ) ;
2017-04-24 16:34:21 -05:00
ret = PTR_ERR ( mgr ) ;
goto err_put_region ;
2016-11-01 14:14:29 -05:00
}
ret = fpga_region_get_bridges ( region , overlay ) ;
if ( ret ) {
pr_err ( " failed to get fpga region bridges \n " ) ;
goto err_put_mgr ;
}
ret = fpga_bridges_disable ( & region - > bridge_list ) ;
if ( ret ) {
pr_err ( " failed to disable region bridges \n " ) ;
goto err_put_br ;
}
ret = fpga_mgr_firmware_load ( mgr , region - > info , firmware_name ) ;
if ( ret ) {
pr_err ( " failed to load fpga image \n " ) ;
goto err_put_br ;
}
ret = fpga_bridges_enable ( & region - > bridge_list ) ;
if ( ret ) {
pr_err ( " failed to enable region bridges \n " ) ;
goto err_put_br ;
}
fpga_mgr_put ( mgr ) ;
fpga_region_put ( region ) ;
return 0 ;
err_put_br :
fpga_bridges_put ( & region - > bridge_list ) ;
err_put_mgr :
fpga_mgr_put ( mgr ) ;
2017-04-24 16:34:21 -05:00
err_put_region :
2016-11-01 14:14:29 -05:00
fpga_region_put ( region ) ;
return ret ;
}
/**
* child_regions_with_firmware
* @ overlay : device node of the overlay
*
* If the overlay adds child FPGA regions , they are not allowed to have
* firmware - name property .
*
* Return 0 for OK or - EINVAL if child FPGA region adds firmware - name .
*/
static int child_regions_with_firmware ( struct device_node * overlay )
{
struct device_node * child_region ;
const char * child_firmware_name ;
int ret = 0 ;
of_node_get ( overlay ) ;
child_region = of_find_matching_node ( overlay , fpga_region_of_match ) ;
while ( child_region ) {
if ( ! of_property_read_string ( child_region , " firmware-name " ,
& child_firmware_name ) ) {
ret = - EINVAL ;
break ;
}
child_region = of_find_matching_node ( child_region ,
fpga_region_of_match ) ;
}
of_node_put ( child_region ) ;
if ( ret )
2017-08-01 21:20:51 -05:00
pr_err ( " firmware-name not allowed in child FPGA region: %pOF " ,
child_region ) ;
2016-11-01 14:14:29 -05:00
return ret ;
}
/**
* fpga_region_notify_pre_apply - pre - apply overlay notification
*
* @ region : FPGA region that the overlay was applied to
* @ nd : overlay notification data
*
* Called after when an overlay targeted to a FPGA Region is about to be
* applied . Function will check the properties that will be added to the FPGA
* region . If the checks pass , it will program the FPGA .
*
* The checks are :
* The overlay must add either firmware - name or external - fpga - config property
* to the FPGA Region .
*
2017-02-27 09:19:02 -06:00
* firmware - name : program the FPGA
* external - fpga - config : FPGA is already programmed
* encrypted - fpga - config : FPGA bitstream is encrypted
2016-11-01 14:14:29 -05:00
*
* The overlay can add other FPGA regions , but child FPGA regions cannot have a
* firmware - name property since those regions don ' t exist yet .
*
* If the overlay that breaks the rules , notifier returns an error and the
* overlay is rejected before it goes into the main tree .
*
* Returns 0 for success or negative error code for failure .
*/
static int fpga_region_notify_pre_apply ( struct fpga_region * region ,
struct of_overlay_notify_data * nd )
{
const char * firmware_name = NULL ;
struct fpga_image_info * info ;
int ret ;
info = devm_kzalloc ( & region - > dev , sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info )
return - ENOMEM ;
region - > info = info ;
/* Reject overlay if child FPGA Regions have firmware-name property */
ret = child_regions_with_firmware ( nd - > overlay ) ;
if ( ret )
return ret ;
/* Read FPGA region properties from the overlay */
if ( of_property_read_bool ( nd - > overlay , " partial-fpga-config " ) )
info - > flags | = FPGA_MGR_PARTIAL_RECONFIG ;
if ( of_property_read_bool ( nd - > overlay , " external-fpga-config " ) )
info - > flags | = FPGA_MGR_EXTERNAL_CONFIG ;
2017-02-27 09:19:02 -06:00
if ( of_property_read_bool ( nd - > overlay , " encrypted-fpga-config " ) )
info - > flags | = FPGA_MGR_ENCRYPTED_BITSTREAM ;
2016-11-01 14:14:29 -05:00
of_property_read_string ( nd - > overlay , " firmware-name " , & firmware_name ) ;
of_property_read_u32 ( nd - > overlay , " region-unfreeze-timeout-us " ,
& info - > enable_timeout_us ) ;
of_property_read_u32 ( nd - > overlay , " region-freeze-timeout-us " ,
& info - > disable_timeout_us ) ;
2017-03-23 19:34:27 -05:00
of_property_read_u32 ( nd - > overlay , " config-complete-timeout-us " ,
& info - > config_complete_timeout_us ) ;
2016-11-01 14:14:29 -05:00
/* If FPGA was externally programmed, don't specify firmware */
if ( ( info - > flags & FPGA_MGR_EXTERNAL_CONFIG ) & & firmware_name ) {
pr_err ( " error: specified firmware and external-fpga-config " ) ;
return - EINVAL ;
}
/* FPGA is already configured externally. We're done. */
if ( info - > flags & FPGA_MGR_EXTERNAL_CONFIG )
return 0 ;
/* If we got this far, we should be programming the FPGA */
if ( ! firmware_name ) {
pr_err ( " should specify firmware-name or external-fpga-config \n " ) ;
return - EINVAL ;
}
return fpga_region_program_fpga ( region , firmware_name , nd - > overlay ) ;
}
/**
* fpga_region_notify_post_remove - post - remove overlay notification
*
* @ region : FPGA region that was targeted by the overlay that was removed
* @ nd : overlay notification data
*
* Called after an overlay has been removed if the overlay ' s target was a
* FPGA region .
*/
static void fpga_region_notify_post_remove ( struct fpga_region * region ,
struct of_overlay_notify_data * nd )
{
fpga_bridges_disable ( & region - > bridge_list ) ;
fpga_bridges_put ( & region - > bridge_list ) ;
devm_kfree ( & region - > dev , region - > info ) ;
region - > info = NULL ;
}
/**
* of_fpga_region_notify - reconfig notifier for dynamic DT changes
* @ nb : notifier block
* @ action : notifier action
* @ arg : reconfig data
*
* This notifier handles programming a FPGA when a " firmware-name " property is
* added to a fpga - region .
*
* Returns NOTIFY_OK or error if FPGA programming fails .
*/
static int of_fpga_region_notify ( struct notifier_block * nb ,
unsigned long action , void * arg )
{
struct of_overlay_notify_data * nd = arg ;
struct fpga_region * region ;
int ret ;
switch ( action ) {
case OF_OVERLAY_PRE_APPLY :
pr_debug ( " %s OF_OVERLAY_PRE_APPLY \n " , __func__ ) ;
break ;
case OF_OVERLAY_POST_APPLY :
pr_debug ( " %s OF_OVERLAY_POST_APPLY \n " , __func__ ) ;
return NOTIFY_OK ; /* not for us */
case OF_OVERLAY_PRE_REMOVE :
pr_debug ( " %s OF_OVERLAY_PRE_REMOVE \n " , __func__ ) ;
return NOTIFY_OK ; /* not for us */
case OF_OVERLAY_POST_REMOVE :
pr_debug ( " %s OF_OVERLAY_POST_REMOVE \n " , __func__ ) ;
break ;
default : /* should not happen */
return NOTIFY_OK ;
}
region = fpga_region_find ( nd - > target ) ;
if ( ! region )
return NOTIFY_OK ;
ret = 0 ;
switch ( action ) {
case OF_OVERLAY_PRE_APPLY :
ret = fpga_region_notify_pre_apply ( region , nd ) ;
break ;
case OF_OVERLAY_POST_REMOVE :
fpga_region_notify_post_remove ( region , nd ) ;
break ;
}
put_device ( & region - > dev ) ;
if ( ret )
return notifier_from_errno ( ret ) ;
return NOTIFY_OK ;
}
static struct notifier_block fpga_region_of_nb = {
. notifier_call = of_fpga_region_notify ,
} ;
static int fpga_region_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct fpga_region * region ;
int id , ret = 0 ;
region = kzalloc ( sizeof ( * region ) , GFP_KERNEL ) ;
if ( ! region )
return - ENOMEM ;
id = ida_simple_get ( & fpga_region_ida , 0 , 0 , GFP_KERNEL ) ;
if ( id < 0 ) {
ret = id ;
goto err_kfree ;
}
mutex_init ( & region - > mutex ) ;
INIT_LIST_HEAD ( & region - > bridge_list ) ;
device_initialize ( & region - > dev ) ;
region - > dev . class = fpga_region_class ;
region - > dev . parent = dev ;
region - > dev . of_node = np ;
region - > dev . id = id ;
dev_set_drvdata ( dev , region ) ;
ret = dev_set_name ( & region - > dev , " region%d " , id ) ;
if ( ret )
goto err_remove ;
ret = device_add ( & region - > dev ) ;
if ( ret )
goto err_remove ;
of_platform_populate ( np , fpga_region_of_match , NULL , & region - > dev ) ;
dev_info ( dev , " FPGA Region probed \n " ) ;
return 0 ;
err_remove :
ida_simple_remove ( & fpga_region_ida , id ) ;
err_kfree :
kfree ( region ) ;
return ret ;
}
static int fpga_region_remove ( struct platform_device * pdev )
{
struct fpga_region * region = platform_get_drvdata ( pdev ) ;
device_unregister ( & region - > dev ) ;
return 0 ;
}
static struct platform_driver fpga_region_driver = {
. probe = fpga_region_probe ,
. remove = fpga_region_remove ,
. driver = {
. name = " fpga-region " ,
. of_match_table = of_match_ptr ( fpga_region_of_match ) ,
} ,
} ;
static void fpga_region_dev_release ( struct device * dev )
{
struct fpga_region * region = to_fpga_region ( dev ) ;
ida_simple_remove ( & fpga_region_ida , region - > dev . id ) ;
kfree ( region ) ;
}
/**
* fpga_region_init - init function for fpga_region class
* Creates the fpga_region class and registers a reconfig notifier .
*/
static int __init fpga_region_init ( void )
{
int ret ;
fpga_region_class = class_create ( THIS_MODULE , " fpga_region " ) ;
if ( IS_ERR ( fpga_region_class ) )
return PTR_ERR ( fpga_region_class ) ;
fpga_region_class - > dev_release = fpga_region_dev_release ;
ret = of_overlay_notifier_register ( & fpga_region_of_nb ) ;
if ( ret )
goto err_class ;
ret = platform_driver_register ( & fpga_region_driver ) ;
if ( ret )
goto err_plat ;
return 0 ;
err_plat :
of_overlay_notifier_unregister ( & fpga_region_of_nb ) ;
err_class :
class_destroy ( fpga_region_class ) ;
ida_destroy ( & fpga_region_ida ) ;
return ret ;
}
static void __exit fpga_region_exit ( void )
{
platform_driver_unregister ( & fpga_region_driver ) ;
of_overlay_notifier_unregister ( & fpga_region_of_nb ) ;
class_destroy ( fpga_region_class ) ;
ida_destroy ( & fpga_region_ida ) ;
}
subsys_initcall ( fpga_region_init ) ;
module_exit ( fpga_region_exit ) ;
MODULE_DESCRIPTION ( " FPGA Region " ) ;
MODULE_AUTHOR ( " Alan Tull <atull@opensource.altera.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;