2017-04-12 19:56:19 +03:00
/*
* vpd . c
*
* Driver for exporting VPD content to sysfs .
*
* Copyright 2017 Google Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License v2 .0 as published by
* the Free Software Foundation .
*
* This program is distributed in the hope that 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 .
*/
# include <linux/ctype.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/kobject.h>
# include <linux/list.h>
# include <linux/module.h>
2017-05-26 23:57:49 +03:00
# include <linux/of_address.h>
# include <linux/platform_device.h>
2017-04-12 19:56:19 +03:00
# include <linux/slab.h>
# include <linux/sysfs.h>
# include "coreboot_table.h"
# include "vpd_decode.h"
# define CB_TAG_VPD 0x2c
# define VPD_CBMEM_MAGIC 0x43524f53
static struct kobject * vpd_kobj ;
struct vpd_cbmem {
u32 magic ;
u32 version ;
u32 ro_size ;
u32 rw_size ;
u8 blob [ 0 ] ;
} ;
struct vpd_section {
bool enabled ;
const char * name ;
char * raw_name ; /* the string name_raw */
struct kobject * kobj ; /* vpd/name directory */
char * baseaddr ;
struct bin_attribute bin_attr ; /* vpd/name_raw bin_attribute */
struct list_head attribs ; /* key/value in vpd_attrib_info list */
} ;
struct vpd_attrib_info {
char * key ;
const char * value ;
struct bin_attribute bin_attr ;
struct list_head list ;
} ;
static struct vpd_section ro_vpd ;
static struct vpd_section rw_vpd ;
static ssize_t vpd_attrib_read ( struct file * filp , struct kobject * kobp ,
struct bin_attribute * bin_attr , char * buf ,
loff_t pos , size_t count )
{
struct vpd_attrib_info * info = bin_attr - > private ;
return memory_read_from_buffer ( buf , count , & pos , info - > value ,
info - > bin_attr . size ) ;
}
/*
* vpd_section_check_key_name ( )
*
* The VPD specification supports only [ a - zA - Z0 - 9 _ ] + characters in key names but
* old firmware versions may have entries like " S/N " which are problematic when
* exporting them as sysfs attributes . These keys present in old firmwares are
* ignored .
*
* Returns VPD_OK for a valid key name , VPD_FAIL otherwise .
*
* @ key : The key name to check
* @ key_len : key name length
*/
static int vpd_section_check_key_name ( const u8 * key , s32 key_len )
{
int c ;
while ( key_len - - > 0 ) {
c = * key + + ;
if ( ! isalnum ( c ) & & c ! = ' _ ' )
return VPD_FAIL ;
}
return VPD_OK ;
}
static int vpd_section_attrib_add ( const u8 * key , s32 key_len ,
const u8 * value , s32 value_len ,
void * arg )
{
int ret ;
struct vpd_section * sec = arg ;
struct vpd_attrib_info * info ;
/*
* Return VPD_OK immediately to decode next entry if the current key
* name contains invalid characters .
*/
if ( vpd_section_check_key_name ( key , key_len ) ! = VPD_OK )
return VPD_OK ;
info = kzalloc ( sizeof ( * info ) , GFP_KERNEL ) ;
2017-05-05 22:08:44 +03:00
if ( ! info )
2017-04-12 19:56:19 +03:00
return - ENOMEM ;
2017-05-24 03:07:42 +03:00
info - > key = kstrndup ( key , key_len , GFP_KERNEL ) ;
2017-05-05 22:08:44 +03:00
if ( ! info - > key ) {
ret = - ENOMEM ;
goto free_info ;
}
2017-04-12 19:56:19 +03:00
sysfs_bin_attr_init ( & info - > bin_attr ) ;
info - > bin_attr . attr . name = info - > key ;
info - > bin_attr . attr . mode = 0444 ;
info - > bin_attr . size = value_len ;
info - > bin_attr . read = vpd_attrib_read ;
info - > bin_attr . private = info ;
info - > value = value ;
INIT_LIST_HEAD ( & info - > list ) ;
ret = sysfs_create_bin_file ( sec - > kobj , & info - > bin_attr ) ;
2017-05-05 22:08:44 +03:00
if ( ret )
goto free_info_key ;
2017-04-12 19:56:19 +03:00
2017-05-24 03:07:41 +03:00
list_add_tail ( & info - > list , & sec - > attribs ) ;
2017-04-12 19:56:19 +03:00
return 0 ;
2017-05-05 22:08:44 +03:00
free_info_key :
kfree ( info - > key ) ;
free_info :
kfree ( info ) ;
return ret ;
2017-04-12 19:56:19 +03:00
}
static void vpd_section_attrib_destroy ( struct vpd_section * sec )
{
struct vpd_attrib_info * info ;
struct vpd_attrib_info * temp ;
list_for_each_entry_safe ( info , temp , & sec - > attribs , list ) {
sysfs_remove_bin_file ( sec - > kobj , & info - > bin_attr ) ;
2017-05-24 03:07:43 +03:00
kfree ( info - > key ) ;
2017-04-12 19:56:19 +03:00
kfree ( info ) ;
}
}
static ssize_t vpd_section_read ( struct file * filp , struct kobject * kobp ,
struct bin_attribute * bin_attr , char * buf ,
loff_t pos , size_t count )
{
struct vpd_section * sec = bin_attr - > private ;
return memory_read_from_buffer ( buf , count , & pos , sec - > baseaddr ,
sec - > bin_attr . size ) ;
}
static int vpd_section_create_attribs ( struct vpd_section * sec )
{
s32 consumed ;
int ret ;
consumed = 0 ;
do {
ret = vpd_decode_string ( sec - > bin_attr . size , sec - > baseaddr ,
& consumed , vpd_section_attrib_add , sec ) ;
} while ( ret = = VPD_OK ) ;
return 0 ;
}
static int vpd_section_init ( const char * name , struct vpd_section * sec ,
phys_addr_t physaddr , size_t size )
{
2017-05-24 03:07:45 +03:00
int err ;
2017-04-12 19:56:19 +03:00
sec - > baseaddr = memremap ( physaddr , size , MEMREMAP_WB ) ;
if ( ! sec - > baseaddr )
return - ENOMEM ;
sec - > name = name ;
/* We want to export the raw partion with name ${name}_raw */
2017-05-24 03:07:45 +03:00
sec - > raw_name = kasprintf ( GFP_KERNEL , " %s_raw " , name ) ;
if ( ! sec - > raw_name ) {
err = - ENOMEM ;
2017-08-11 02:13:13 +03:00
goto err_memunmap ;
2017-05-24 03:07:45 +03:00
}
2017-04-12 19:56:19 +03:00
sysfs_bin_attr_init ( & sec - > bin_attr ) ;
sec - > bin_attr . attr . name = sec - > raw_name ;
sec - > bin_attr . attr . mode = 0444 ;
sec - > bin_attr . size = size ;
sec - > bin_attr . read = vpd_section_read ;
sec - > bin_attr . private = sec ;
2017-05-24 03:07:45 +03:00
err = sysfs_create_bin_file ( vpd_kobj , & sec - > bin_attr ) ;
if ( err )
goto err_free_raw_name ;
2017-04-12 19:56:19 +03:00
sec - > kobj = kobject_create_and_add ( name , vpd_kobj ) ;
if ( ! sec - > kobj ) {
2017-05-24 03:07:45 +03:00
err = - EINVAL ;
goto err_sysfs_remove ;
2017-04-12 19:56:19 +03:00
}
INIT_LIST_HEAD ( & sec - > attribs ) ;
vpd_section_create_attribs ( sec ) ;
sec - > enabled = true ;
return 0 ;
2017-05-24 03:07:45 +03:00
err_sysfs_remove :
2017-04-12 19:56:19 +03:00
sysfs_remove_bin_file ( vpd_kobj , & sec - > bin_attr ) ;
2017-05-24 03:07:45 +03:00
err_free_raw_name :
2017-04-12 19:56:19 +03:00
kfree ( sec - > raw_name ) ;
2017-08-11 02:13:13 +03:00
err_memunmap :
memunmap ( sec - > baseaddr ) ;
2017-05-24 03:07:45 +03:00
return err ;
2017-04-12 19:56:19 +03:00
}
static int vpd_section_destroy ( struct vpd_section * sec )
{
if ( sec - > enabled ) {
vpd_section_attrib_destroy ( sec ) ;
2017-05-24 03:07:44 +03:00
kobject_put ( sec - > kobj ) ;
2017-04-12 19:56:19 +03:00
sysfs_remove_bin_file ( vpd_kobj , & sec - > bin_attr ) ;
kfree ( sec - > raw_name ) ;
2017-08-11 02:13:13 +03:00
memunmap ( sec - > baseaddr ) ;
2017-04-12 19:56:19 +03:00
}
return 0 ;
}
static int vpd_sections_init ( phys_addr_t physaddr )
{
struct vpd_cbmem __iomem * temp ;
struct vpd_cbmem header ;
int ret = 0 ;
temp = memremap ( physaddr , sizeof ( struct vpd_cbmem ) , MEMREMAP_WB ) ;
if ( ! temp )
return - ENOMEM ;
memcpy_fromio ( & header , temp , sizeof ( struct vpd_cbmem ) ) ;
2017-08-11 02:13:13 +03:00
memunmap ( temp ) ;
2017-04-12 19:56:19 +03:00
if ( header . magic ! = VPD_CBMEM_MAGIC )
return - ENODEV ;
if ( header . ro_size ) {
ret = vpd_section_init ( " ro " , & ro_vpd ,
physaddr + sizeof ( struct vpd_cbmem ) ,
header . ro_size ) ;
if ( ret )
return ret ;
}
if ( header . rw_size ) {
ret = vpd_section_init ( " rw " , & rw_vpd ,
physaddr + sizeof ( struct vpd_cbmem ) +
header . ro_size , header . rw_size ) ;
2017-05-26 23:57:49 +03:00
if ( ret )
2017-04-12 19:56:19 +03:00
return ret ;
}
return 0 ;
}
2017-05-26 23:57:49 +03:00
static int vpd_probe ( struct platform_device * pdev )
2017-04-12 19:56:19 +03:00
{
2017-05-26 23:57:49 +03:00
int ret ;
2017-05-24 03:07:47 +03:00
struct lb_cbmem_ref entry ;
2017-05-26 23:57:49 +03:00
ret = coreboot_table_find ( CB_TAG_VPD , & entry , sizeof ( entry ) ) ;
if ( ret )
return ret ;
return vpd_sections_init ( entry . cbmem_addr ) ;
}
static struct platform_driver vpd_driver = {
. probe = vpd_probe ,
. driver = {
. name = " vpd " ,
} ,
} ;
static int __init vpd_platform_init ( void )
{
struct platform_device * pdev ;
pdev = platform_device_register_simple ( " vpd " , - 1 , NULL , 0 ) ;
if ( IS_ERR ( pdev ) )
return PTR_ERR ( pdev ) ;
2017-04-12 19:56:19 +03:00
vpd_kobj = kobject_create_and_add ( " vpd " , firmware_kobj ) ;
if ( ! vpd_kobj )
return - ENOMEM ;
2017-05-26 23:57:49 +03:00
platform_driver_register ( & vpd_driver ) ;
2017-04-12 19:56:19 +03:00
return 0 ;
}
static void __exit vpd_platform_exit ( void )
{
vpd_section_destroy ( & ro_vpd ) ;
vpd_section_destroy ( & rw_vpd ) ;
2017-05-24 03:07:44 +03:00
kobject_put ( vpd_kobj ) ;
2017-04-12 19:56:19 +03:00
}
module_init ( vpd_platform_init ) ;
module_exit ( vpd_platform_exit ) ;
MODULE_AUTHOR ( " Google, Inc. " ) ;
MODULE_LICENSE ( " GPL " ) ;