2005-04-17 02:20:36 +04:00
/*
* Interfaces to retrieve and set PDC Stable options ( firmware )
*
2006-01-11 23:59:53 +03:00
* Copyright ( C ) 2005 - 2006 Thibaut VARENE < varenet @ parisc - linux . org >
2005-04-17 02:20:36 +04:00
*
* This program is free software ; you can redistribute it and / or modify
2006-02-04 04:06:30 +03:00
* it under the terms of the GNU General Public License , version 2 , as
* published by the Free Software Foundation .
2005-04-17 02:20:36 +04:00
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
*
* DEV NOTE : the PDC Procedures reference states that :
* " A minimum of 96 bytes of Stable Storage is required. Providing more than
* 96 bytes of Stable Storage is optional [ . . . ] . Failure to provide the
* optional locations from 96 to 192 results in the loss of certain
* functionality during boot . "
*
* Since locations between 96 and 192 are the various paths , most ( if not
* all ) PA - RISC machines should have them . Anyway , for safety reasons , the
2006-01-11 23:59:53 +03:00
* following code can deal with just 96 bytes of Stable Storage , and all
2005-04-17 02:20:36 +04:00
* sizes between 96 and 192 bytes ( provided they are multiple of struct
* device_path size , eg : 128 , 160 and 192 ) to provide full information .
2006-05-05 04:43:34 +04:00
* One last word : there ' s one path we can always count on : the primary path .
* Anything above 224 bytes is used for ' osdep2 ' OS - dependent storage area .
*
* The first OS - dependent area should always be available . Obviously , this is
* not true for the other one . Also bear in mind that reading / writing from / to
* osdep2 is much more expensive than from / to osdep1 .
* NOTE : We do not handle the 2 bytes OS - dep area at 0x5D , nor the first
* 2 bytes of storage available right after OSID . That ' s a total of 4 bytes
* sacrificed : - ETOOLAZY : P
2006-01-11 23:59:53 +03:00
*
* The current policy wrt file permissions is :
* - write : root only
* - read : ( reading triggers PDC calls ) ? root only : everyone
* The rationale is that PDC calls could hog ( DoS ) the machine .
*
* TODO :
* - timer / fastsize write calls
2005-04-17 02:20:36 +04:00
*/
# undef PDCS_DEBUG
# ifdef PDCS_DEBUG
# define DPRINTK(fmt, args...) printk(KERN_DEBUG fmt, ## args)
# else
# define DPRINTK(fmt, args...)
# endif
# include <linux/module.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/string.h>
2006-01-11 23:17:46 +03:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <linux/ctype.h>
# include <linux/sysfs.h>
# include <linux/kobject.h>
# include <linux/device.h>
# include <linux/errno.h>
2006-01-11 23:59:53 +03:00
# include <linux/spinlock.h>
2005-04-17 02:20:36 +04:00
# include <asm/pdc.h>
# include <asm/page.h>
# include <asm/uaccess.h>
# include <asm/hardware.h>
2006-05-05 04:43:34 +04:00
# define PDCS_VERSION "0.30"
2006-01-11 23:59:53 +03:00
# define PDCS_PREFIX "PDC Stable Storage"
2005-04-17 02:20:36 +04:00
# define PDCS_ADDR_PPRI 0x00
# define PDCS_ADDR_OSID 0x40
2006-05-05 04:43:34 +04:00
# define PDCS_ADDR_OSD1 0x48
# define PDCS_ADDR_DIAG 0x58
2005-04-17 02:20:36 +04:00
# define PDCS_ADDR_FSIZ 0x5C
# define PDCS_ADDR_PCON 0x60
# define PDCS_ADDR_PALT 0x80
# define PDCS_ADDR_PKBD 0xA0
2006-05-05 04:43:34 +04:00
# define PDCS_ADDR_OSD2 0xE0
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Thibaut VARENE <varenet@parisc-linux.org> " ) ;
MODULE_DESCRIPTION ( " sysfs interface to HP PDC Stable Storage data " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( PDCS_VERSION ) ;
2006-01-11 23:59:53 +03:00
/* holds Stable Storage size. Initialized once and for all, no lock needed */
2006-01-11 04:35:03 +03:00
static unsigned long pdcs_size __read_mostly ;
2005-04-17 02:20:36 +04:00
2006-05-05 04:43:34 +04:00
/* holds OS ID. Initialized once and for all, hopefully to 0x0006 */
static u16 pdcs_osid __read_mostly ;
2005-04-17 02:20:36 +04:00
/* This struct defines what we need to deal with a parisc pdc path entry */
struct pdcspath_entry {
2006-01-11 23:59:53 +03:00
rwlock_t rw_lock ; /* to protect path entry access */
2005-04-17 02:20:36 +04:00
short ready ; /* entry record is valid if != 0 */
unsigned long addr ; /* entry address in stable storage */
char * name ; /* entry name */
struct device_path devpath ; /* device path in parisc representation */
struct device * dev ; /* corresponding device */
struct kobject kobj ;
} ;
struct pdcspath_attribute {
struct attribute attr ;
ssize_t ( * show ) ( struct pdcspath_entry * entry , char * buf ) ;
ssize_t ( * store ) ( struct pdcspath_entry * entry , const char * buf , size_t count ) ;
} ;
# define PDCSPATH_ENTRY(_addr, _name) \
struct pdcspath_entry pdcspath_entry_ # # _name = { \
. ready = 0 , \
. addr = _addr , \
. name = __stringify ( _name ) , \
} ;
# define PDCS_ATTR(_name, _mode, _show, _store) \
struct subsys_attribute pdcs_attr_ # # _name = { \
. attr = { . name = __stringify ( _name ) , . mode = _mode , . owner = THIS_MODULE } , \
. show = _show , \
. store = _store , \
} ;
# define PATHS_ATTR(_name, _mode, _show, _store) \
struct pdcspath_attribute paths_attr_ # # _name = { \
. attr = { . name = __stringify ( _name ) , . mode = _mode , . owner = THIS_MODULE } , \
. show = _show , \
. store = _store , \
} ;
# define to_pdcspath_attribute(_attr) container_of(_attr, struct pdcspath_attribute, attr)
# define to_pdcspath_entry(obj) container_of(obj, struct pdcspath_entry, kobj)
/**
* pdcspath_fetch - This function populates the path entry structs .
* @ entry : A pointer to an allocated pdcspath_entry .
*
* The general idea is that you don ' t read from the Stable Storage every time
* you access the files provided by the facilites . We store a copy of the
* content of the stable storage WRT various paths in these structs . We read
* these structs when reading the files , and we will write to these structs when
* writing to the files , and only then write them back to the Stable Storage .
2006-01-11 23:59:53 +03:00
*
* This function expects to be called with @ entry - > rw_lock write - hold .
2005-04-17 02:20:36 +04:00
*/
static int
pdcspath_fetch ( struct pdcspath_entry * entry )
{
struct device_path * devpath ;
if ( ! entry )
return - EINVAL ;
devpath = & entry - > devpath ;
DPRINTK ( " %s: fetch: 0x%p, 0x%p, addr: 0x%lx \n " , __func__ ,
entry , devpath , entry - > addr ) ;
/* addr, devpath and count must be word aligned */
if ( pdc_stable_read ( entry - > addr , devpath , sizeof ( * devpath ) ) ! = PDC_OK )
return - EIO ;
/* Find the matching device.
NOTE : hardware_path overlays with device_path , so the nice cast can
be used */
entry - > dev = hwpath_to_device ( ( struct hardware_path * ) devpath ) ;
entry - > ready = 1 ;
DPRINTK ( " %s: device: 0x%p \n " , __func__ , entry - > dev ) ;
return 0 ;
}
/**
* pdcspath_store - This function writes a path to stable storage .
* @ entry : A pointer to an allocated pdcspath_entry .
*
* It can be used in two ways : either by passing it a preset devpath struct
* containing an already computed hardware path , or by passing it a device
* pointer , from which it ' ll find out the corresponding hardware path .
* For now we do not handle the case where there ' s an error in writing to the
* Stable Storage area , so you ' d better not mess up the data : P
2006-01-11 23:59:53 +03:00
*
* This function expects to be called with @ entry - > rw_lock write - hold .
2005-04-17 02:20:36 +04:00
*/
2006-01-11 23:59:53 +03:00
static void
2005-04-17 02:20:36 +04:00
pdcspath_store ( struct pdcspath_entry * entry )
{
struct device_path * devpath ;
2006-01-11 23:59:53 +03:00
BUG_ON ( ! entry ) ;
2005-04-17 02:20:36 +04:00
devpath = & entry - > devpath ;
/* We expect the caller to set the ready flag to 0 if the hardware
path struct provided is invalid , so that we know we have to fill it .
First case , we don ' t have a preset hwpath . . . */
if ( ! entry - > ready ) {
/* ...but we have a device, map it */
2006-01-11 23:59:53 +03:00
BUG_ON ( ! entry - > dev ) ;
device_to_hwpath ( entry - > dev , ( struct hardware_path * ) devpath ) ;
2005-04-17 02:20:36 +04:00
}
/* else, we expect the provided hwpath to be valid. */
DPRINTK ( " %s: store: 0x%p, 0x%p, addr: 0x%lx \n " , __func__ ,
entry , devpath , entry - > addr ) ;
/* addr, devpath and count must be word aligned */
if ( pdc_stable_write ( entry - > addr , devpath , sizeof ( * devpath ) ) ! = PDC_OK ) {
printk ( KERN_ERR " %s: an error occured when writing to PDC. \n "
" It is likely that the Stable Storage data has been corrupted. \n "
" Please check it carefully upon next reboot. \n " , __func__ ) ;
2006-01-11 23:59:53 +03:00
WARN_ON ( 1 ) ;
2005-04-17 02:20:36 +04:00
}
2006-01-11 04:48:01 +03:00
/* kobject is already registered */
entry - > ready = 2 ;
2005-04-17 02:20:36 +04:00
DPRINTK ( " %s: device: 0x%p \n " , __func__ , entry - > dev ) ;
}
/**
* pdcspath_hwpath_read - This function handles hardware path pretty printing .
* @ entry : An allocated and populated pdscpath_entry struct .
* @ buf : The output buffer to write to .
*
* We will call this function to format the output of the hwpath attribute file .
*/
static ssize_t
pdcspath_hwpath_read ( struct pdcspath_entry * entry , char * buf )
{
char * out = buf ;
struct device_path * devpath ;
2006-01-11 23:59:53 +03:00
short i ;
2005-04-17 02:20:36 +04:00
if ( ! entry | | ! buf )
return - EINVAL ;
2006-01-11 23:59:53 +03:00
read_lock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
devpath = & entry - > devpath ;
2006-01-11 23:59:53 +03:00
i = entry - > ready ;
read_unlock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
if ( ! i ) /* entry is not ready */
2005-04-17 02:20:36 +04:00
return - ENODATA ;
for ( i = 0 ; i < 6 ; i + + ) {
if ( devpath - > bc [ i ] > = 128 )
continue ;
out + = sprintf ( out , " %u/ " , ( unsigned char ) devpath - > bc [ i ] ) ;
}
out + = sprintf ( out , " %u \n " , ( unsigned char ) devpath - > mod ) ;
return out - buf ;
}
/**
* pdcspath_hwpath_write - This function handles hardware path modifying .
* @ entry : An allocated and populated pdscpath_entry struct .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
*
* We will call this function to change the current hardware path .
* Hardware paths are to be given ' / ' - delimited , without brackets .
2006-01-11 23:59:53 +03:00
* We make sure that the provided path actually maps to an existing
2005-04-17 02:20:36 +04:00
* device , BUT nothing would prevent some foolish user to set the path to some
* PCI bridge or even a CPU . . .
* A better work around would be to make sure we are at the end of a device tree
* for instance , but it would be IMHO beyond the simple scope of that driver .
* The aim is to provide a facility . Data correctness is left to userland .
*/
static ssize_t
pdcspath_hwpath_write ( struct pdcspath_entry * entry , const char * buf , size_t count )
{
struct hardware_path hwpath ;
unsigned short i ;
char in [ count + 1 ] , * temp ;
struct device * dev ;
if ( ! entry | | ! buf | | ! count )
return - EINVAL ;
/* We'll use a local copy of buf */
memset ( in , 0 , count + 1 ) ;
strncpy ( in , buf , count ) ;
/* Let's clean up the target. 0xff is a blank pattern */
memset ( & hwpath , 0xff , sizeof ( hwpath ) ) ;
/* First, pick the mod field (the last one of the input string) */
if ( ! ( temp = strrchr ( in , ' / ' ) ) )
return - EINVAL ;
hwpath . mod = simple_strtoul ( temp + 1 , NULL , 10 ) ;
in [ temp - in ] = ' \0 ' ; /* truncate the remaining string. just precaution */
DPRINTK ( " %s: mod: %d \n " , __func__ , hwpath . mod ) ;
/* Then, loop for each delimiter, making sure we don't have too many.
we write the bc fields in a down - top way . No matter what , we stop
before writing the last field . If there are too many fields anyway ,
then the user is a moron and it ' ll be caught up later when we ' ll
check the consistency of the given hwpath . */
for ( i = 5 ; ( ( temp = strrchr ( in , ' / ' ) ) ) & & ( temp - in > 0 ) & & ( likely ( i ) ) ; i - - ) {
hwpath . bc [ i ] = simple_strtoul ( temp + 1 , NULL , 10 ) ;
in [ temp - in ] = ' \0 ' ;
DPRINTK ( " %s: bc[%d]: %d \n " , __func__ , i , hwpath . bc [ i ] ) ;
}
/* Store the final field */
hwpath . bc [ i ] = simple_strtoul ( in , NULL , 10 ) ;
DPRINTK ( " %s: bc[%d]: %d \n " , __func__ , i , hwpath . bc [ i ] ) ;
/* Now we check that the user isn't trying to lure us */
if ( ! ( dev = hwpath_to_device ( ( struct hardware_path * ) & hwpath ) ) ) {
printk ( KERN_WARNING " %s: attempt to set invalid \" %s \" "
" hardware path: %s \n " , __func__ , entry - > name , buf ) ;
return - EINVAL ;
}
/* So far so good, let's get in deep */
2006-01-11 23:59:53 +03:00
write_lock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
entry - > ready = 0 ;
entry - > dev = dev ;
/* Now, dive in. Write back to the hardware */
2006-01-11 23:59:53 +03:00
pdcspath_store ( entry ) ;
2005-04-17 02:20:36 +04:00
/* Update the symlink to the real device */
sysfs_remove_link ( & entry - > kobj , " device " ) ;
sysfs_create_link ( & entry - > kobj , & entry - > dev - > kobj , " device " ) ;
2006-01-11 23:59:53 +03:00
write_unlock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
printk ( KERN_INFO PDCS_PREFIX " : changed \" %s \" path to \" %s \" \n " ,
2005-04-17 02:20:36 +04:00
entry - > name , buf ) ;
return count ;
}
/**
* pdcspath_layer_read - Extended layer ( eg . SCSI ids ) pretty printing .
* @ entry : An allocated and populated pdscpath_entry struct .
* @ buf : The output buffer to write to .
*
* We will call this function to format the output of the layer attribute file .
*/
static ssize_t
pdcspath_layer_read ( struct pdcspath_entry * entry , char * buf )
{
char * out = buf ;
struct device_path * devpath ;
2006-01-11 23:59:53 +03:00
short i ;
2005-04-17 02:20:36 +04:00
if ( ! entry | | ! buf )
return - EINVAL ;
2006-01-11 23:59:53 +03:00
read_lock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
devpath = & entry - > devpath ;
2006-01-11 23:59:53 +03:00
i = entry - > ready ;
read_unlock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
if ( ! i ) /* entry is not ready */
2005-04-17 02:20:36 +04:00
return - ENODATA ;
for ( i = 0 ; devpath - > layers [ i ] & & ( likely ( i < 6 ) ) ; i + + )
out + = sprintf ( out , " %u " , devpath - > layers [ i ] ) ;
out + = sprintf ( out , " \n " ) ;
return out - buf ;
}
/**
* pdcspath_layer_write - This function handles extended layer modifying .
* @ entry : An allocated and populated pdscpath_entry struct .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
*
* We will call this function to change the current layer value .
* Layers are to be given ' . ' - delimited , without brackets .
* XXX beware we are far less checky WRT input data provided than for hwpath .
* Potential harm can be done , since there ' s no way to check the validity of
* the layer fields .
*/
static ssize_t
pdcspath_layer_write ( struct pdcspath_entry * entry , const char * buf , size_t count )
{
unsigned int layers [ 6 ] ; /* device-specific info (ctlr#, unit#, ...) */
unsigned short i ;
char in [ count + 1 ] , * temp ;
if ( ! entry | | ! buf | | ! count )
return - EINVAL ;
/* We'll use a local copy of buf */
memset ( in , 0 , count + 1 ) ;
strncpy ( in , buf , count ) ;
/* Let's clean up the target. 0 is a blank pattern */
memset ( & layers , 0 , sizeof ( layers ) ) ;
/* First, pick the first layer */
if ( unlikely ( ! isdigit ( * in ) ) )
return - EINVAL ;
layers [ 0 ] = simple_strtoul ( in , NULL , 10 ) ;
DPRINTK ( " %s: layer[0]: %d \n " , __func__ , layers [ 0 ] ) ;
temp = in ;
for ( i = 1 ; ( ( temp = strchr ( temp , ' . ' ) ) ) & & ( likely ( i < 6 ) ) ; i + + ) {
if ( unlikely ( ! isdigit ( * ( + + temp ) ) ) )
return - EINVAL ;
layers [ i ] = simple_strtoul ( temp , NULL , 10 ) ;
DPRINTK ( " %s: layer[%d]: %d \n " , __func__ , i , layers [ i ] ) ;
}
/* So far so good, let's get in deep */
2006-01-11 23:59:53 +03:00
write_lock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
/* First, overwrite the current layers with the new ones, not touching
the hardware path . */
memcpy ( & entry - > devpath . layers , & layers , sizeof ( layers ) ) ;
/* Now, dive in. Write back to the hardware */
2006-01-11 23:59:53 +03:00
pdcspath_store ( entry ) ;
write_unlock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
printk ( KERN_INFO PDCS_PREFIX " : changed \" %s \" layers to \" %s \" \n " ,
2005-04-17 02:20:36 +04:00
entry - > name , buf ) ;
return count ;
}
/**
* pdcspath_attr_show - Generic read function call wrapper .
* @ kobj : The kobject to get info from .
* @ attr : The attribute looked upon .
* @ buf : The output buffer .
*/
static ssize_t
pdcspath_attr_show ( struct kobject * kobj , struct attribute * attr , char * buf )
{
struct pdcspath_entry * entry = to_pdcspath_entry ( kobj ) ;
struct pdcspath_attribute * pdcs_attr = to_pdcspath_attribute ( attr ) ;
ssize_t ret = 0 ;
if ( pdcs_attr - > show )
ret = pdcs_attr - > show ( entry , buf ) ;
return ret ;
}
/**
* pdcspath_attr_store - Generic write function call wrapper .
* @ kobj : The kobject to write info to .
* @ attr : The attribute to be modified .
* @ buf : The input buffer .
* @ count : The size of the buffer .
*/
static ssize_t
pdcspath_attr_store ( struct kobject * kobj , struct attribute * attr ,
const char * buf , size_t count )
{
struct pdcspath_entry * entry = to_pdcspath_entry ( kobj ) ;
struct pdcspath_attribute * pdcs_attr = to_pdcspath_attribute ( attr ) ;
ssize_t ret = 0 ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EACCES ;
if ( pdcs_attr - > store )
ret = pdcs_attr - > store ( entry , buf , count ) ;
return ret ;
}
static struct sysfs_ops pdcspath_attr_ops = {
. show = pdcspath_attr_show ,
. store = pdcspath_attr_store ,
} ;
/* These are the two attributes of any PDC path. */
2006-01-11 23:59:53 +03:00
static PATHS_ATTR ( hwpath , 0644 , pdcspath_hwpath_read , pdcspath_hwpath_write ) ;
static PATHS_ATTR ( layer , 0644 , pdcspath_layer_read , pdcspath_layer_write ) ;
2005-04-17 02:20:36 +04:00
static struct attribute * paths_subsys_attrs [ ] = {
& paths_attr_hwpath . attr ,
& paths_attr_layer . attr ,
NULL ,
} ;
/* Specific kobject type for our PDC paths */
static struct kobj_type ktype_pdcspath = {
. sysfs_ops = & pdcspath_attr_ops ,
. default_attrs = paths_subsys_attrs ,
} ;
/* We hard define the 4 types of path we expect to find */
static PDCSPATH_ENTRY ( PDCS_ADDR_PPRI , primary ) ;
static PDCSPATH_ENTRY ( PDCS_ADDR_PCON , console ) ;
static PDCSPATH_ENTRY ( PDCS_ADDR_PALT , alternative ) ;
static PDCSPATH_ENTRY ( PDCS_ADDR_PKBD , keyboard ) ;
/* An array containing all PDC paths we will deal with */
static struct pdcspath_entry * pdcspath_entries [ ] = {
& pdcspath_entry_primary ,
& pdcspath_entry_alternative ,
& pdcspath_entry_console ,
& pdcspath_entry_keyboard ,
NULL ,
} ;
2006-01-11 23:59:53 +03:00
/* For more insight of what's going on here, refer to PDC Procedures doc,
* Section PDC_STABLE */
2005-04-17 02:20:36 +04:00
/**
2006-01-11 23:59:53 +03:00
* pdcs_size_read - Stable Storage size output .
2005-04-17 02:20:36 +04:00
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*/
static ssize_t
2006-01-11 23:59:53 +03:00
pdcs_size_read ( struct subsystem * entry , char * buf )
2005-04-17 02:20:36 +04:00
{
char * out = buf ;
if ( ! entry | | ! buf )
return - EINVAL ;
/* show the size of the stable storage */
2006-01-11 23:59:53 +03:00
out + = sprintf ( out , " %ld \n " , pdcs_size ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
return out - buf ;
}
/**
* pdcs_auto_read - Stable Storage autoboot / search flag output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
* @ knob : The PF_AUTOBOOT or PF_AUTOSEARCH flag
*/
static ssize_t
pdcs_auto_read ( struct subsystem * entry , char * buf , int knob )
{
char * out = buf ;
struct pdcspath_entry * pathentry ;
2006-03-27 23:52:14 +04:00
2006-01-11 23:59:53 +03:00
if ( ! entry | | ! buf )
return - EINVAL ;
/* Current flags are stored in primary boot path entry */
pathentry = & pdcspath_entry_primary ;
read_lock ( & pathentry - > rw_lock ) ;
out + = sprintf ( out , " %s \n " , ( pathentry - > devpath . flags & knob ) ?
" On " : " Off " ) ;
read_unlock ( & pathentry - > rw_lock ) ;
return out - buf ;
}
/**
* pdcs_autoboot_read - Stable Storage autoboot flag output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*/
static inline ssize_t
pdcs_autoboot_read ( struct subsystem * entry , char * buf )
{
return pdcs_auto_read ( entry , buf , PF_AUTOBOOT ) ;
}
/**
* pdcs_autosearch_read - Stable Storage autoboot flag output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*/
static inline ssize_t
pdcs_autosearch_read ( struct subsystem * entry , char * buf )
{
return pdcs_auto_read ( entry , buf , PF_AUTOSEARCH ) ;
}
/**
* pdcs_timer_read - Stable Storage timer count output ( in seconds ) .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*
* The value of the timer field correponds to a number of seconds in powers of 2.
*/
static ssize_t
pdcs_timer_read ( struct subsystem * entry , char * buf )
{
char * out = buf ;
struct pdcspath_entry * pathentry ;
if ( ! entry | | ! buf )
return - EINVAL ;
/* Current flags are stored in primary boot path entry */
pathentry = & pdcspath_entry_primary ;
/* print the timer value in seconds */
read_lock ( & pathentry - > rw_lock ) ;
out + = sprintf ( out , " %u \n " , ( pathentry - > devpath . flags & PF_TIMER ) ?
( 1 < < ( pathentry - > devpath . flags & PF_TIMER ) ) : 0 ) ;
read_unlock ( & pathentry - > rw_lock ) ;
return out - buf ;
}
/**
* pdcs_osid_read - Stable Storage OS ID register output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*/
static ssize_t
pdcs_osid_read ( struct subsystem * entry , char * buf )
{
char * out = buf ;
char * tmpstr = NULL ;
if ( ! entry | | ! buf )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
2006-05-05 04:43:34 +04:00
switch ( pdcs_osid ) {
2006-05-05 04:43:34 +04:00
case 0x0000 : tmpstr = " No OS " ; break ;
case 0x0001 : tmpstr = " HP-UX " ; break ;
case 0x0002 : tmpstr = " MPE-iX " ; break ;
case 0x0003 : tmpstr = " OSF " ; break ;
case 0x0004 : tmpstr = " HP-RT " ; break ;
case 0x0005 : tmpstr = " Novell Netware " ; break ;
case 0x0006 : tmpstr = " Linux " ; break ;
2005-04-17 02:20:36 +04:00
default : tmpstr = " Unknown " ; break ;
}
2006-05-05 04:43:34 +04:00
out + = sprintf ( out , " %s dependent data (0x%.4x) \n " , tmpstr , pdcs_osid ) ;
2006-05-05 04:43:34 +04:00
return out - buf ;
}
/**
* pdcs_osdep1_read - Stable Storage OS - Dependent data area 1 output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*
* This can hold 16 bytes of OS - Dependent data .
*/
static ssize_t
pdcs_osdep1_read ( struct subsystem * entry , char * buf )
{
char * out = buf ;
u32 result [ 4 ] ;
if ( ! entry | | ! buf )
return - EINVAL ;
if ( pdc_stable_read ( PDCS_ADDR_OSD1 , & result , sizeof ( result ) ) ! = PDC_OK )
return - EIO ;
out + = sprintf ( out , " 0x%.8x \n " , result [ 0 ] ) ;
out + = sprintf ( out , " 0x%.8x \n " , result [ 1 ] ) ;
out + = sprintf ( out , " 0x%.8x \n " , result [ 2 ] ) ;
out + = sprintf ( out , " 0x%.8x \n " , result [ 3 ] ) ;
return out - buf ;
}
/**
* pdcs_diagnostic_read - Stable Storage Diagnostic register output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*
* I have NFC how to interpret the content of that register ; - ) .
*/
static ssize_t
pdcs_diagnostic_read ( struct subsystem * entry , char * buf )
{
char * out = buf ;
u32 result ;
if ( ! entry | | ! buf )
return - EINVAL ;
/* get diagnostic */
if ( pdc_stable_read ( PDCS_ADDR_DIAG , & result , sizeof ( result ) ) ! = PDC_OK )
return - EIO ;
out + = sprintf ( out , " 0x%.4x \n " , ( result > > 16 ) ) ;
2006-01-11 23:59:53 +03:00
return out - buf ;
}
/**
* pdcs_fastsize_read - Stable Storage FastSize register output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*
* This register holds the amount of system RAM to be tested during boot sequence .
*/
static ssize_t
pdcs_fastsize_read ( struct subsystem * entry , char * buf )
{
char * out = buf ;
2006-05-05 04:43:34 +04:00
u32 result ;
2006-01-11 23:59:53 +03:00
if ( ! entry | | ! buf )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
/* get fast-size */
if ( pdc_stable_read ( PDCS_ADDR_FSIZ , & result , sizeof ( result ) ) ! = PDC_OK )
return - EIO ;
if ( ( result & 0x0F ) < 0x0E )
2005-10-22 06:57:13 +04:00
out + = sprintf ( out , " %d kB " , ( 1 < < ( result & 0x0F ) ) * 256 ) ;
2005-04-17 02:20:36 +04:00
else
out + = sprintf ( out , " All " ) ;
out + = sprintf ( out , " \n " ) ;
return out - buf ;
}
2006-05-05 04:43:34 +04:00
/**
* pdcs_osdep2_read - Stable Storage OS - Dependent data area 2 output .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The output buffer to write to .
*
* This can hold pdcs_size - 224 bytes of OS - Dependent data , when available .
*/
static ssize_t
pdcs_osdep2_read ( struct subsystem * entry , char * buf )
{
char * out = buf ;
unsigned long size ;
unsigned short i ;
u32 result ;
if ( unlikely ( pdcs_size < = 224 ) )
return - ENODATA ;
size = pdcs_size - 224 ;
if ( ! entry | | ! buf )
return - EINVAL ;
for ( i = 0 ; i < size ; i + = 4 ) {
if ( unlikely ( pdc_stable_read ( PDCS_ADDR_OSD2 + i , & result ,
sizeof ( result ) ) ! = PDC_OK ) )
return - EIO ;
out + = sprintf ( out , " 0x%.8x \n " , result ) ;
}
return out - buf ;
}
2005-04-17 02:20:36 +04:00
/**
2006-01-11 23:59:53 +03:00
* pdcs_auto_write - This function handles autoboot / search flag modifying .
2005-04-17 02:20:36 +04:00
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
2006-01-11 23:59:53 +03:00
* @ knob : The PF_AUTOBOOT or PF_AUTOSEARCH flag
2005-04-17 02:20:36 +04:00
*
2006-01-11 23:59:53 +03:00
* We will call this function to change the current autoboot flag .
2005-04-17 02:20:36 +04:00
* We expect a precise syntax :
2006-01-11 23:59:53 +03:00
* \ " n \" (n == 0 or 1) to toggle AutoBoot Off or On
2005-04-17 02:20:36 +04:00
*/
static ssize_t
2006-01-11 23:59:53 +03:00
pdcs_auto_write ( struct subsystem * entry , const char * buf , size_t count , int knob )
2005-04-17 02:20:36 +04:00
{
struct pdcspath_entry * pathentry ;
unsigned char flags ;
char in [ count + 1 ] , * temp ;
char c ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EACCES ;
if ( ! entry | | ! buf | | ! count )
return - EINVAL ;
/* We'll use a local copy of buf */
memset ( in , 0 , count + 1 ) ;
strncpy ( in , buf , count ) ;
/* Current flags are stored in primary boot path entry */
pathentry = & pdcspath_entry_primary ;
/* Be nice to the existing flag record */
2006-01-11 23:59:53 +03:00
read_lock ( & pathentry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
flags = pathentry - > devpath . flags ;
2006-01-11 23:59:53 +03:00
read_unlock ( & pathentry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
DPRINTK ( " %s: flags before: 0x%X \n " , __func__ , flags ) ;
temp = in ;
while ( * temp & & isspace ( * temp ) )
temp + + ;
c = * temp + + - ' 0 ' ;
if ( ( c ! = 0 ) & & ( c ! = 1 ) )
goto parse_error ;
if ( c = = 0 )
2006-01-11 23:59:53 +03:00
flags & = ~ knob ;
2005-04-17 02:20:36 +04:00
else
2006-01-11 23:59:53 +03:00
flags | = knob ;
2005-04-17 02:20:36 +04:00
DPRINTK ( " %s: flags after: 0x%X \n " , __func__ , flags ) ;
/* So far so good, let's get in deep */
2006-01-11 23:59:53 +03:00
write_lock ( & pathentry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
/* Change the path entry flags first */
pathentry - > devpath . flags = flags ;
/* Now, dive in. Write back to the hardware */
2006-01-11 23:59:53 +03:00
pdcspath_store ( pathentry ) ;
write_unlock ( & pathentry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
printk ( KERN_INFO PDCS_PREFIX " : changed \" %s \" to \" %s \" \n " ,
( knob & PF_AUTOBOOT ) ? " autoboot " : " autosearch " ,
( flags & knob ) ? " On " : " Off " ) ;
2005-04-17 02:20:36 +04:00
return count ;
parse_error :
2006-01-11 23:59:53 +03:00
printk ( KERN_WARNING " %s: Parse error: expect \" n \" (n == 0 or 1) \n " , __func__ ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2006-01-11 23:59:53 +03:00
/**
* pdcs_autoboot_write - This function handles autoboot flag modifying .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
*
* We will call this function to change the current boot flags .
* We expect a precise syntax :
* \ " n \" (n == 0 or 1) to toggle AutoSearch Off or On
*/
static inline ssize_t
pdcs_autoboot_write ( struct subsystem * entry , const char * buf , size_t count )
{
return pdcs_auto_write ( entry , buf , count , PF_AUTOBOOT ) ;
}
/**
* pdcs_autosearch_write - This function handles autosearch flag modifying .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
*
* We will call this function to change the current boot flags .
* We expect a precise syntax :
* \ " n \" (n == 0 or 1) to toggle AutoSearch Off or On
*/
static inline ssize_t
pdcs_autosearch_write ( struct subsystem * entry , const char * buf , size_t count )
{
return pdcs_auto_write ( entry , buf , count , PF_AUTOSEARCH ) ;
}
2006-05-05 04:43:34 +04:00
/**
* pdcs_osdep1_write - Stable Storage OS - Dependent data area 1 input .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
*
* This can store 16 bytes of OS - Dependent data . We use a byte - by - byte
* write approach . It ' s up to userspace to deal with it when constructing
* its input buffer .
*/
static ssize_t
pdcs_osdep1_write ( struct subsystem * entry , const char * buf , size_t count )
{
u8 in [ 16 ] ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EACCES ;
if ( ! entry | | ! buf | | ! count )
return - EINVAL ;
2006-06-21 23:27:29 +04:00
if ( unlikely ( pdcs_osid ! = OS_ID_LINUX ) )
2006-05-05 04:43:34 +04:00
return - EPERM ;
if ( count > 16 )
return - EMSGSIZE ;
/* We'll use a local copy of buf */
memset ( in , 0 , 16 ) ;
memcpy ( in , buf , count ) ;
if ( pdc_stable_write ( PDCS_ADDR_OSD1 , & in , sizeof ( in ) ) ! = PDC_OK )
return - EIO ;
return count ;
}
/**
* pdcs_osdep2_write - Stable Storage OS - Dependent data area 2 input .
* @ entry : An allocated and populated subsytem struct . We don ' t use it tho .
* @ buf : The input buffer to read from .
* @ count : The number of bytes to be read .
*
* This can store pdcs_size - 224 bytes of OS - Dependent data . We use a
* byte - by - byte write approach . It ' s up to userspace to deal with it when
* constructing its input buffer .
*/
static ssize_t
pdcs_osdep2_write ( struct subsystem * entry , const char * buf , size_t count )
{
unsigned long size ;
unsigned short i ;
u8 in [ 4 ] ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EACCES ;
if ( ! entry | | ! buf | | ! count )
return - EINVAL ;
if ( unlikely ( pdcs_size < = 224 ) )
return - ENOSYS ;
2006-06-21 23:27:29 +04:00
if ( unlikely ( pdcs_osid ! = OS_ID_LINUX ) )
2006-05-05 04:43:34 +04:00
return - EPERM ;
size = pdcs_size - 224 ;
if ( count > size )
return - EMSGSIZE ;
/* We'll use a local copy of buf */
for ( i = 0 ; i < count ; i + = 4 ) {
memset ( in , 0 , 4 ) ;
memcpy ( in , buf + i , ( count - i < 4 ) ? count - i : 4 ) ;
if ( unlikely ( pdc_stable_write ( PDCS_ADDR_OSD2 + i , & in ,
sizeof ( in ) ) ! = PDC_OK ) )
return - EIO ;
}
return count ;
}
2006-01-11 23:59:53 +03:00
/* The remaining attributes. */
static PDCS_ATTR ( size , 0444 , pdcs_size_read , NULL ) ;
static PDCS_ATTR ( autoboot , 0644 , pdcs_autoboot_read , pdcs_autoboot_write ) ;
static PDCS_ATTR ( autosearch , 0644 , pdcs_autosearch_read , pdcs_autosearch_write ) ;
static PDCS_ATTR ( timer , 0444 , pdcs_timer_read , NULL ) ;
2006-05-05 04:43:34 +04:00
static PDCS_ATTR ( osid , 0444 , pdcs_osid_read , NULL ) ;
static PDCS_ATTR ( osdep1 , 0600 , pdcs_osdep1_read , pdcs_osdep1_write ) ;
static PDCS_ATTR ( diagnostic , 0400 , pdcs_diagnostic_read , NULL ) ;
2006-01-11 23:59:53 +03:00
static PDCS_ATTR ( fastsize , 0400 , pdcs_fastsize_read , NULL ) ;
2006-05-05 04:43:34 +04:00
static PDCS_ATTR ( osdep2 , 0600 , pdcs_osdep2_read , pdcs_osdep2_write ) ;
2005-04-17 02:20:36 +04:00
static struct subsys_attribute * pdcs_subsys_attrs [ ] = {
2006-01-11 23:59:53 +03:00
& pdcs_attr_size ,
& pdcs_attr_autoboot ,
& pdcs_attr_autosearch ,
& pdcs_attr_timer ,
& pdcs_attr_osid ,
2006-05-05 04:43:34 +04:00
& pdcs_attr_osdep1 ,
& pdcs_attr_diagnostic ,
2006-01-11 23:59:53 +03:00
& pdcs_attr_fastsize ,
2006-05-05 04:43:34 +04:00
& pdcs_attr_osdep2 ,
2006-01-11 23:59:53 +03:00
NULL ,
2005-04-17 02:20:36 +04:00
} ;
static decl_subsys ( paths , & ktype_pdcspath , NULL ) ;
2006-01-11 23:59:53 +03:00
static decl_subsys ( stable , NULL , NULL ) ;
2005-04-17 02:20:36 +04:00
/**
* pdcs_register_pathentries - Prepares path entries kobjects for sysfs usage .
*
* It creates kobjects corresponding to each path entry with nice sysfs
* links to the real device . This is where the magic takes place : when
* registering the subsystem attributes during module init , each kobject hereby
* created will show in the sysfs tree as a folder containing files as defined
* by path_subsys_attr [ ] .
*/
static inline int __init
pdcs_register_pathentries ( void )
{
unsigned short i ;
struct pdcspath_entry * entry ;
2006-01-11 04:48:01 +03:00
int err ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
/* Initialize the entries rw_lock before anything else */
for ( i = 0 ; ( entry = pdcspath_entries [ i ] ) ; i + + )
rwlock_init ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; ( entry = pdcspath_entries [ i ] ) ; i + + ) {
2006-01-11 23:59:53 +03:00
write_lock ( & entry - > rw_lock ) ;
err = pdcspath_fetch ( entry ) ;
write_unlock ( & entry - > rw_lock ) ;
if ( err < 0 )
2005-04-17 02:20:36 +04:00
continue ;
2006-01-11 04:48:01 +03:00
if ( ( err = kobject_set_name ( & entry - > kobj , " %s " , entry - > name ) ) )
return err ;
2005-04-17 02:20:36 +04:00
kobj_set_kset_s ( entry , paths_subsys ) ;
2006-01-11 04:48:01 +03:00
if ( ( err = kobject_register ( & entry - > kobj ) ) )
return err ;
/* kobject is now registered */
2006-01-11 23:59:53 +03:00
write_lock ( & entry - > rw_lock ) ;
2006-01-11 04:48:01 +03:00
entry - > ready = 2 ;
2005-04-17 02:20:36 +04:00
/* Add a nice symlink to the real device */
2006-01-11 23:59:53 +03:00
if ( entry - > dev )
sysfs_create_link ( & entry - > kobj , & entry - > dev - > kobj , " device " ) ;
write_unlock ( & entry - > rw_lock ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
/**
* pdcs_unregister_pathentries - Routine called when unregistering the module .
*/
2006-01-11 04:48:01 +03:00
static inline void
2005-04-17 02:20:36 +04:00
pdcs_unregister_pathentries ( void )
{
unsigned short i ;
struct pdcspath_entry * entry ;
2006-01-11 23:59:53 +03:00
for ( i = 0 ; ( entry = pdcspath_entries [ i ] ) ; i + + ) {
read_lock ( & entry - > rw_lock ) ;
2006-01-11 04:48:01 +03:00
if ( entry - > ready > = 2 )
2006-01-11 23:59:53 +03:00
kobject_unregister ( & entry - > kobj ) ;
read_unlock ( & entry - > rw_lock ) ;
}
2005-04-17 02:20:36 +04:00
}
/*
2006-01-11 23:59:53 +03:00
* For now we register the stable subsystem with the firmware subsystem
* and the paths subsystem with the stable subsystem
2005-04-17 02:20:36 +04:00
*/
static int __init
pdc_stable_init ( void )
{
struct subsys_attribute * attr ;
int i , rc = 0 , error = 0 ;
2006-05-05 04:43:34 +04:00
u32 result ;
2005-04-17 02:20:36 +04:00
/* find the size of the stable storage */
if ( pdc_stable_get_size ( & pdcs_size ) ! = PDC_OK )
return - ENODEV ;
2006-01-11 23:59:53 +03:00
/* make sure we have enough data */
if ( pdcs_size < 96 )
return - ENODATA ;
printk ( KERN_INFO PDCS_PREFIX " facility v%s \n " , PDCS_VERSION ) ;
2005-04-17 02:20:36 +04:00
2006-05-05 04:43:34 +04:00
/* get OSID */
if ( pdc_stable_read ( PDCS_ADDR_OSID , & result , sizeof ( result ) ) ! = PDC_OK )
return - EIO ;
/* the actual result is 16 bits away */
pdcs_osid = ( u16 ) ( result > > 16 ) ;
2006-01-11 23:59:53 +03:00
/* For now we'll register the stable subsys within this driver */
if ( ( rc = firmware_register ( & stable_subsys ) ) )
2006-01-11 04:48:01 +03:00
goto fail_firmreg ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
/* Don't forget the root entries */
2005-04-17 02:20:36 +04:00
for ( i = 0 ; ( attr = pdcs_subsys_attrs [ i ] ) & & ! error ; i + + )
if ( attr - > show )
2006-01-11 23:59:53 +03:00
error = subsys_create_file ( & stable_subsys , attr ) ;
2005-04-17 02:20:36 +04:00
2006-01-11 23:59:53 +03:00
/* register the paths subsys as a subsystem of stable subsys */
kset_set_kset_s ( & paths_subsys , stable_subsys ) ;
2006-05-05 04:43:34 +04:00
if ( ( rc = subsystem_register ( & paths_subsys ) ) )
2006-01-11 04:48:01 +03:00
goto fail_subsysreg ;
2005-04-17 02:20:36 +04:00
/* now we create all "files" for the paths subsys */
2006-01-11 04:48:01 +03:00
if ( ( rc = pdcs_register_pathentries ( ) ) )
goto fail_pdcsreg ;
return rc ;
2005-04-17 02:20:36 +04:00
2006-01-11 04:48:01 +03:00
fail_pdcsreg :
pdcs_unregister_pathentries ( ) ;
subsystem_unregister ( & paths_subsys ) ;
fail_subsysreg :
2006-01-11 23:59:53 +03:00
firmware_unregister ( & stable_subsys ) ;
2006-01-11 04:48:01 +03:00
fail_firmreg :
2006-01-11 23:59:53 +03:00
printk ( KERN_INFO PDCS_PREFIX " bailing out \n " ) ;
2006-01-11 04:48:01 +03:00
return rc ;
2005-04-17 02:20:36 +04:00
}
static void __exit
pdc_stable_exit ( void )
{
pdcs_unregister_pathentries ( ) ;
subsystem_unregister ( & paths_subsys ) ;
2006-01-11 23:59:53 +03:00
firmware_unregister ( & stable_subsys ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( pdc_stable_init ) ;
module_exit ( pdc_stable_exit ) ;