2005-04-17 02:20:36 +04:00
/*
* csr1212 . c - - IEEE 1212 Control and Status Register support for Linux
*
* Copyright ( C ) 2003 Francois Retief < fgretief @ sun . ac . za >
* Steve Kinneberg < kinnebergsteve @ acmsystems . com >
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice ,
* this list of conditions and the following disclaimer .
* 2. Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO ,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ;
* OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ,
* WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR
* OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
/* TODO List:
* - Verify interface consistency : i . e . , public functions that take a size
* parameter expect size to be in bytes .
* - Convenience functions for reading a block of data from a given offset .
*/
# ifndef __KERNEL__
# include <string.h>
# endif
# include "csr1212.h"
/* Permitted key type for each key id */
# define __I (1 << CSR1212_KV_TYPE_IMMEDIATE)
# define __C (1 << CSR1212_KV_TYPE_CSR_OFFSET)
# define __D (1 << CSR1212_KV_TYPE_DIRECTORY)
# define __L (1 << CSR1212_KV_TYPE_LEAF)
static const u_int8_t csr1212_key_id_type_map [ 0x30 ] = {
0 , /* Reserved */
__D | __L , /* Descriptor */
__I | __D | __L , /* Bus_Dependent_Info */
__I | __D | __L , /* Vendor */
__I , /* Hardware_Version */
0 , 0 , /* Reserved */
__D | __L , /* Module */
0 , 0 , 0 , 0 , /* Reserved */
__I , /* Node_Capabilities */
__L , /* EUI_64 */
0 , 0 , 0 , /* Reserved */
__D , /* Unit */
__I , /* Specifier_ID */
__I , /* Version */
__I | __C | __D | __L , /* Dependent_Info */
__L , /* Unit_Location */
0 , /* Reserved */
__I , /* Model */
__D , /* Instance */
__L , /* Keyword */
__D , /* Feature */
__L , /* Extended_ROM */
__I , /* Extended_Key_Specifier_ID */
__I , /* Extended_Key */
__I | __C | __D | __L , /* Extended_Data */
__L , /* Modifiable_Descriptor */
__I , /* Directory_ID */
__I , /* Revision */
} ;
# undef __I
# undef __C
# undef __D
# undef __L
# define quads_to_bytes(_q) ((_q) * sizeof(u_int32_t))
# define bytes_to_quads(_b) (((_b) + sizeof(u_int32_t) - 1) / sizeof(u_int32_t))
static inline void free_keyval ( struct csr1212_keyval * kv )
{
if ( ( kv - > key . type = = CSR1212_KV_TYPE_LEAF ) & &
( kv - > key . id ! = CSR1212_KV_ID_EXTENDED_ROM ) )
CSR1212_FREE ( kv - > value . leaf . data ) ;
CSR1212_FREE ( kv ) ;
}
static u_int16_t csr1212_crc16 ( const u_int32_t * buffer , size_t length )
{
int shift ;
u_int32_t data ;
u_int16_t sum , crc = 0 ;
for ( ; length ; length - - ) {
data = CSR1212_BE32_TO_CPU ( * buffer ) ;
buffer + + ;
for ( shift = 28 ; shift > = 0 ; shift - = 4 ) {
sum = ( ( crc > > 12 ) ^ ( data > > shift ) ) & 0xf ;
crc = ( crc < < 4 ) ^ ( sum < < 12 ) ^ ( sum < < 5 ) ^ ( sum ) ;
}
crc & = 0xffff ;
}
return CSR1212_CPU_TO_BE16 ( crc ) ;
}
#if 0
/* Microsoft computes the CRC with the bytes in reverse order. Therefore we
* have a special version of the CRC algorithm to account for their buggy
* software . */
static u_int16_t csr1212_msft_crc16 ( const u_int32_t * buffer , size_t length )
{
int shift ;
u_int32_t data ;
u_int16_t sum , crc = 0 ;
for ( ; length ; length - - ) {
data = CSR1212_LE32_TO_CPU ( * buffer ) ;
buffer + + ;
for ( shift = 28 ; shift > = 0 ; shift - = 4 ) {
sum = ( ( crc > > 12 ) ^ ( data > > shift ) ) & 0xf ;
crc = ( crc < < 4 ) ^ ( sum < < 12 ) ^ ( sum < < 5 ) ^ ( sum ) ;
}
crc & = 0xffff ;
}
return CSR1212_CPU_TO_BE16 ( crc ) ;
}
# endif
static inline struct csr1212_dentry * csr1212_find_keyval ( struct csr1212_keyval * dir ,
struct csr1212_keyval * kv )
{
struct csr1212_dentry * pos ;
for ( pos = dir - > value . directory . dentries_head ;
pos ! = NULL ; pos = pos - > next ) {
if ( pos - > kv = = kv )
return pos ;
}
return NULL ;
}
static inline struct csr1212_keyval * csr1212_find_keyval_offset ( struct csr1212_keyval * kv_list ,
u_int32_t offset )
{
struct csr1212_keyval * kv ;
for ( kv = kv_list - > next ; kv & & ( kv ! = kv_list ) ; kv = kv - > next ) {
if ( kv - > offset = = offset )
return kv ;
}
return NULL ;
}
/* Creation Routines */
struct csr1212_csr * csr1212_create_csr ( struct csr1212_bus_ops * ops ,
size_t bus_info_size , void * private )
{
struct csr1212_csr * csr ;
csr = CSR1212_MALLOC ( sizeof ( * csr ) ) ;
if ( ! csr )
return NULL ;
csr - > cache_head =
csr1212_rom_cache_malloc ( CSR1212_CONFIG_ROM_SPACE_OFFSET ,
CSR1212_CONFIG_ROM_SPACE_SIZE ) ;
if ( ! csr - > cache_head ) {
CSR1212_FREE ( csr ) ;
return NULL ;
}
/* The keyval key id is not used for the root node, but a valid key id
* that can be used for a directory needs to be passed to
* csr1212_new_directory ( ) . */
csr - > root_kv = csr1212_new_directory ( CSR1212_KV_ID_VENDOR ) ;
if ( ! csr - > root_kv ) {
CSR1212_FREE ( csr - > cache_head ) ;
CSR1212_FREE ( csr ) ;
return NULL ;
}
csr - > bus_info_data = csr - > cache_head - > data ;
csr - > bus_info_len = bus_info_size ;
csr - > crc_len = bus_info_size ;
csr - > ops = ops ;
csr - > private = private ;
csr - > cache_tail = csr - > cache_head ;
return csr ;
}
void csr1212_init_local_csr ( struct csr1212_csr * csr ,
const u_int32_t * bus_info_data , int max_rom )
{
static const int mr_map [ ] = { 4 , 64 , 1024 , 0 } ;
2005-07-10 04:01:23 +04:00
# ifdef __KERNEL__
BUG_ON ( max_rom & ~ 0x3 ) ;
2005-04-17 02:20:36 +04:00
csr - > max_rom = mr_map [ max_rom ] ;
2005-07-10 04:01:23 +04:00
# else
if ( max_rom & ~ 0x3 ) /* caller supplied invalid argument */
csr - > max_rom = 0 ;
else
csr - > max_rom = mr_map [ max_rom ] ;
# endif
2005-04-17 02:20:36 +04:00
memcpy ( csr - > bus_info_data , bus_info_data , csr - > bus_info_len ) ;
}
static struct csr1212_keyval * csr1212_new_keyval ( u_int8_t type , u_int8_t key )
{
struct csr1212_keyval * kv ;
if ( key < 0x30 & & ( ( csr1212_key_id_type_map [ key ] & ( 1 < < type ) ) = = 0 ) )
return NULL ;
kv = CSR1212_MALLOC ( sizeof ( * kv ) ) ;
if ( ! kv )
return NULL ;
kv - > key . type = type ;
kv - > key . id = key ;
kv - > associate = NULL ;
kv - > refcnt = 1 ;
kv - > next = NULL ;
kv - > prev = NULL ;
kv - > offset = 0 ;
kv - > valid = 0 ;
return kv ;
}
struct csr1212_keyval * csr1212_new_immediate ( u_int8_t key , u_int32_t value )
{
struct csr1212_keyval * kv = csr1212_new_keyval ( CSR1212_KV_TYPE_IMMEDIATE , key ) ;
if ( ! kv )
return NULL ;
kv - > value . immediate = value ;
kv - > valid = 1 ;
return kv ;
}
struct csr1212_keyval * csr1212_new_leaf ( u_int8_t key , const void * data , size_t data_len )
{
struct csr1212_keyval * kv = csr1212_new_keyval ( CSR1212_KV_TYPE_LEAF , key ) ;
if ( ! kv )
return NULL ;
if ( data_len > 0 ) {
kv - > value . leaf . data = CSR1212_MALLOC ( data_len ) ;
if ( ! kv - > value . leaf . data ) {
CSR1212_FREE ( kv ) ;
return NULL ;
}
if ( data )
memcpy ( kv - > value . leaf . data , data , data_len ) ;
} else {
kv - > value . leaf . data = NULL ;
}
kv - > value . leaf . len = bytes_to_quads ( data_len ) ;
kv - > offset = 0 ;
kv - > valid = 1 ;
return kv ;
}
struct csr1212_keyval * csr1212_new_csr_offset ( u_int8_t key , u_int32_t csr_offset )
{
struct csr1212_keyval * kv = csr1212_new_keyval ( CSR1212_KV_TYPE_CSR_OFFSET , key ) ;
if ( ! kv )
return NULL ;
kv - > value . csr_offset = csr_offset ;
kv - > offset = 0 ;
kv - > valid = 1 ;
return kv ;
}
struct csr1212_keyval * csr1212_new_directory ( u_int8_t key )
{
struct csr1212_keyval * kv = csr1212_new_keyval ( CSR1212_KV_TYPE_DIRECTORY , key ) ;
if ( ! kv )
return NULL ;
kv - > value . directory . len = 0 ;
kv - > offset = 0 ;
kv - > value . directory . dentries_head = NULL ;
kv - > value . directory . dentries_tail = NULL ;
kv - > valid = 1 ;
return kv ;
}
int csr1212_associate_keyval ( struct csr1212_keyval * kv ,
struct csr1212_keyval * associate )
{
if ( ! kv | | ! associate )
return CSR1212_EINVAL ;
if ( kv - > key . id = = CSR1212_KV_ID_DESCRIPTOR | |
( associate - > key . id ! = CSR1212_KV_ID_DESCRIPTOR & &
associate - > key . id ! = CSR1212_KV_ID_DEPENDENT_INFO & &
associate - > key . id ! = CSR1212_KV_ID_EXTENDED_KEY & &
associate - > key . id ! = CSR1212_KV_ID_EXTENDED_DATA & &
associate - > key . id < 0x30 ) )
return CSR1212_EINVAL ;
if ( kv - > key . id = = CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID & &
associate - > key . id ! = CSR1212_KV_ID_EXTENDED_KEY )
return CSR1212_EINVAL ;
if ( kv - > key . id = = CSR1212_KV_ID_EXTENDED_KEY & &
associate - > key . id ! = CSR1212_KV_ID_EXTENDED_DATA )
return CSR1212_EINVAL ;
if ( associate - > key . id = = CSR1212_KV_ID_EXTENDED_KEY & &
kv - > key . id ! = CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID )
return CSR1212_EINVAL ;
if ( associate - > key . id = = CSR1212_KV_ID_EXTENDED_DATA & &
kv - > key . id ! = CSR1212_KV_ID_EXTENDED_KEY )
return CSR1212_EINVAL ;
if ( kv - > associate )
csr1212_release_keyval ( kv - > associate ) ;
associate - > refcnt + + ;
kv - > associate = associate ;
return CSR1212_SUCCESS ;
}
int csr1212_attach_keyval_to_directory ( struct csr1212_keyval * dir ,
struct csr1212_keyval * kv )
{
struct csr1212_dentry * dentry ;
if ( ! kv | | ! dir | | dir - > key . type ! = CSR1212_KV_TYPE_DIRECTORY )
return CSR1212_EINVAL ;
dentry = CSR1212_MALLOC ( sizeof ( * dentry ) ) ;
if ( ! dentry )
return CSR1212_ENOMEM ;
dentry - > kv = kv ;
kv - > refcnt + + ;
dentry - > next = NULL ;
dentry - > prev = dir - > value . directory . dentries_tail ;
if ( ! dir - > value . directory . dentries_head )
dir - > value . directory . dentries_head = dentry ;
if ( dir - > value . directory . dentries_tail )
dir - > value . directory . dentries_tail - > next = dentry ;
dir - > value . directory . dentries_tail = dentry ;
return CSR1212_SUCCESS ;
}
struct csr1212_keyval * csr1212_new_extended_immediate ( u_int32_t spec , u_int32_t key ,
u_int32_t value )
{
struct csr1212_keyval * kvs , * kvk , * kvv ;
kvs = csr1212_new_immediate ( CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID , spec ) ;
kvk = csr1212_new_immediate ( CSR1212_KV_ID_EXTENDED_KEY , key ) ;
kvv = csr1212_new_immediate ( CSR1212_KV_ID_EXTENDED_DATA , value ) ;
if ( ! kvs | | ! kvk | | ! kvv ) {
if ( kvs )
free_keyval ( kvs ) ;
if ( kvk )
free_keyval ( kvk ) ;
if ( kvv )
free_keyval ( kvv ) ;
return NULL ;
}
/* Don't keep a local reference to the extended key or value. */
kvk - > refcnt = 0 ;
kvv - > refcnt = 0 ;
csr1212_associate_keyval ( kvk , kvv ) ;
csr1212_associate_keyval ( kvs , kvk ) ;
return kvs ;
}
struct csr1212_keyval * csr1212_new_extended_leaf ( u_int32_t spec , u_int32_t key ,
const void * data , size_t data_len )
{
struct csr1212_keyval * kvs , * kvk , * kvv ;
kvs = csr1212_new_immediate ( CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID , spec ) ;
kvk = csr1212_new_immediate ( CSR1212_KV_ID_EXTENDED_KEY , key ) ;
kvv = csr1212_new_leaf ( CSR1212_KV_ID_EXTENDED_DATA , data , data_len ) ;
if ( ! kvs | | ! kvk | | ! kvv ) {
if ( kvs )
free_keyval ( kvs ) ;
if ( kvk )
free_keyval ( kvk ) ;
if ( kvv )
free_keyval ( kvv ) ;
return NULL ;
}
/* Don't keep a local reference to the extended key or value. */
kvk - > refcnt = 0 ;
kvv - > refcnt = 0 ;
csr1212_associate_keyval ( kvk , kvv ) ;
csr1212_associate_keyval ( kvs , kvk ) ;
return kvs ;
}
struct csr1212_keyval * csr1212_new_descriptor_leaf ( u_int8_t dtype , u_int32_t specifier_id ,
const void * data , size_t data_len )
{
struct csr1212_keyval * kv ;
kv = csr1212_new_leaf ( CSR1212_KV_ID_DESCRIPTOR , NULL ,
data_len + CSR1212_DESCRIPTOR_LEAF_OVERHEAD ) ;
if ( ! kv )
return NULL ;
CSR1212_DESCRIPTOR_LEAF_SET_TYPE ( kv , dtype ) ;
CSR1212_DESCRIPTOR_LEAF_SET_SPECIFIER_ID ( kv , specifier_id ) ;
if ( data ) {
memcpy ( CSR1212_DESCRIPTOR_LEAF_DATA ( kv ) , data , data_len ) ;
}
return kv ;
}
struct csr1212_keyval * csr1212_new_textual_descriptor_leaf ( u_int8_t cwidth ,
u_int16_t cset ,
u_int16_t language ,
const void * data ,
size_t data_len )
{
struct csr1212_keyval * kv ;
char * lstr ;
kv = csr1212_new_descriptor_leaf ( 0 , 0 , NULL , data_len +
CSR1212_TEXTUAL_DESCRIPTOR_LEAF_OVERHEAD ) ;
if ( ! kv )
return NULL ;
CSR1212_TEXTUAL_DESCRIPTOR_LEAF_SET_WIDTH ( kv , cwidth ) ;
CSR1212_TEXTUAL_DESCRIPTOR_LEAF_SET_CHAR_SET ( kv , cset ) ;
CSR1212_TEXTUAL_DESCRIPTOR_LEAF_SET_LANGUAGE ( kv , language ) ;
lstr = ( char * ) CSR1212_TEXTUAL_DESCRIPTOR_LEAF_DATA ( kv ) ;
/* make sure last quadlet is zeroed out */
* ( ( u_int32_t * ) & ( lstr [ ( data_len - 1 ) & ~ 0x3 ] ) ) = 0 ;
/* don't copy the NUL terminator */
memcpy ( lstr , data , data_len ) ;
return kv ;
}
static int csr1212_check_minimal_ascii ( const char * s )
{
static const char minimal_ascii_table [ ] = {
0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x07 ,
0x00 , 0x00 , 0x0a , 0x00 , 0x0C , 0x0D , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x20 , 0x21 , 0x22 , 0x00 , 0x00 , 0x25 , 0x26 , 0x27 ,
0x28 , 0x29 , 0x2a , 0x2b , 0x2c , 0x2d , 0x2e , 0x2f ,
0x30 , 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x36 , 0x37 ,
0x38 , 0x39 , 0x3a , 0x3b , 0x3c , 0x3d , 0x3e , 0x3f ,
0x40 , 0x41 , 0x42 , 0x43 , 0x44 , 0x45 , 0x46 , 0x47 ,
0x48 , 0x49 , 0x4a , 0x4b , 0x4c , 0x4d , 0x4e , 0x4f ,
0x50 , 0x51 , 0x52 , 0x53 , 0x54 , 0x55 , 0x56 , 0x57 ,
0x58 , 0x59 , 0x5a , 0x00 , 0x00 , 0x00 , 0x00 , 0x5f ,
0x00 , 0x61 , 0x62 , 0x63 , 0x64 , 0x65 , 0x66 , 0x67 ,
0x68 , 0x69 , 0x6a , 0x6b , 0x6c , 0x6d , 0x6e , 0x6f ,
0x70 , 0x71 , 0x72 , 0x73 , 0x74 , 0x75 , 0x76 , 0x77 ,
0x78 , 0x79 , 0x7a , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
} ;
for ( ; * s ; s + + ) {
if ( minimal_ascii_table [ * s & 0x7F ] ! = * s )
return - 1 ; /* failed */
}
/* String conforms to minimal-ascii, as specified by IEEE 1212,
* par . 7.4 */
return 0 ;
}
struct csr1212_keyval * csr1212_new_string_descriptor_leaf ( const char * s )
{
/* Check if string conform to minimal_ascii format */
if ( csr1212_check_minimal_ascii ( s ) )
return NULL ;
/* IEEE 1212, par. 7.5.4.1 Textual descriptors (minimal ASCII) */
return csr1212_new_textual_descriptor_leaf ( 0 , 0 , 0 , s , strlen ( s ) ) ;
}
struct csr1212_keyval * csr1212_new_icon_descriptor_leaf ( u_int32_t version ,
u_int8_t palette_depth ,
u_int8_t color_space ,
u_int16_t language ,
u_int16_t hscan ,
u_int16_t vscan ,
u_int32_t * palette ,
u_int32_t * pixels )
{
static const int pd [ 4 ] = { 0 , 4 , 16 , 256 } ;
static const int cs [ 16 ] = { 4 , 2 } ;
struct csr1212_keyval * kv ;
2005-07-10 04:01:23 +04:00
int palette_size ;
2005-04-17 02:20:36 +04:00
int pixel_size = ( hscan * vscan + 3 ) & ~ 0x3 ;
2005-07-10 04:01:23 +04:00
if ( ! pixels | | ( ! palette & & palette_depth ) | |
( palette_depth & ~ 0x3 ) | | ( color_space & ~ 0xf ) )
2005-04-17 02:20:36 +04:00
return NULL ;
2005-07-10 04:01:23 +04:00
palette_size = pd [ palette_depth ] * cs [ color_space ] ;
2005-04-17 02:20:36 +04:00
kv = csr1212_new_descriptor_leaf ( 1 , 0 , NULL ,
palette_size + pixel_size +
CSR1212_ICON_DESCRIPTOR_LEAF_OVERHEAD ) ;
if ( ! kv )
return NULL ;
CSR1212_ICON_DESCRIPTOR_LEAF_SET_VERSION ( kv , version ) ;
CSR1212_ICON_DESCRIPTOR_LEAF_SET_PALETTE_DEPTH ( kv , palette_depth ) ;
CSR1212_ICON_DESCRIPTOR_LEAF_SET_COLOR_SPACE ( kv , color_space ) ;
CSR1212_ICON_DESCRIPTOR_LEAF_SET_LANGUAGE ( kv , language ) ;
CSR1212_ICON_DESCRIPTOR_LEAF_SET_HSCAN ( kv , hscan ) ;
CSR1212_ICON_DESCRIPTOR_LEAF_SET_VSCAN ( kv , vscan ) ;
if ( palette_size )
memcpy ( CSR1212_ICON_DESCRIPTOR_LEAF_PALETTE ( kv ) , palette ,
palette_size ) ;
memcpy ( CSR1212_ICON_DESCRIPTOR_LEAF_PIXELS ( kv ) , pixels , pixel_size ) ;
return kv ;
}
struct csr1212_keyval * csr1212_new_modifiable_descriptor_leaf ( u_int16_t max_size ,
u_int64_t address )
{
struct csr1212_keyval * kv ;
/* IEEE 1212, par. 7.5.4.3 Modifiable descriptors */
kv = csr1212_new_leaf ( CSR1212_KV_ID_MODIFIABLE_DESCRIPTOR , NULL , sizeof ( u_int64_t ) ) ;
if ( ! kv )
return NULL ;
CSR1212_MODIFIABLE_DESCRIPTOR_SET_MAX_SIZE ( kv , max_size ) ;
CSR1212_MODIFIABLE_DESCRIPTOR_SET_ADDRESS_HI ( kv , address ) ;
CSR1212_MODIFIABLE_DESCRIPTOR_SET_ADDRESS_LO ( kv , address ) ;
return kv ;
}
static int csr1212_check_keyword ( const char * s )
{
for ( ; * s ; s + + ) {
if ( ( ' A ' < = * s ) & & ( * s < = ' Z ' ) )
continue ;
if ( ( ' 0 ' < = * s ) & & ( * s < = ' 9 ' ) )
continue ;
if ( * s = = ' - ' )
continue ;
return - 1 ; /* failed */
}
/* String conforms to keyword, as specified by IEEE 1212,
* par . 7.6 .5 */
return CSR1212_SUCCESS ;
}
struct csr1212_keyval * csr1212_new_keyword_leaf ( int strc , const char * strv [ ] )
{
struct csr1212_keyval * kv ;
char * buffer ;
int i , data_len = 0 ;
/* Check all keywords to see if they conform to restrictions:
* Only the following characters is allowed [ ' A ' . . ' Z ' , ' 0 ' . . ' 9 ' , ' - ' ]
* Each word is zero - terminated .
* Also calculate the total length of the keywords .
*/
for ( i = 0 ; i < strc ; i + + ) {
if ( ! strv [ i ] | | csr1212_check_keyword ( strv [ i ] ) ) {
return NULL ;
}
data_len + = strlen ( strv [ i ] ) + 1 ; /* Add zero-termination char. */
}
/* IEEE 1212, par. 7.6.5 Keyword leaves */
kv = csr1212_new_leaf ( CSR1212_KV_ID_KEYWORD , NULL , data_len ) ;
if ( ! kv )
return NULL ;
buffer = ( char * ) kv - > value . leaf . data ;
/* make sure last quadlet is zeroed out */
* ( ( u_int32_t * ) & ( buffer [ ( data_len - 1 ) & ~ 0x3 ] ) ) = 0 ;
/* Copy keyword(s) into leaf data buffer */
for ( i = 0 ; i < strc ; i + + ) {
int len = strlen ( strv [ i ] ) + 1 ;
memcpy ( buffer , strv [ i ] , len ) ;
buffer + = len ;
}
return kv ;
}
/* Destruction Routines */
void csr1212_detach_keyval_from_directory ( struct csr1212_keyval * dir ,
struct csr1212_keyval * kv )
{
struct csr1212_dentry * dentry ;
if ( ! kv | | ! dir | | dir - > key . type ! = CSR1212_KV_TYPE_DIRECTORY )
return ;
dentry = csr1212_find_keyval ( dir , kv ) ;
if ( ! dentry )
return ;
if ( dentry - > prev )
dentry - > prev - > next = dentry - > next ;
if ( dentry - > next )
dentry - > next - > prev = dentry - > prev ;
if ( dir - > value . directory . dentries_head = = dentry )
dir - > value . directory . dentries_head = dentry - > next ;
if ( dir - > value . directory . dentries_tail = = dentry )
dir - > value . directory . dentries_tail = dentry - > prev ;
CSR1212_FREE ( dentry ) ;
csr1212_release_keyval ( kv ) ;
}
void csr1212_disassociate_keyval ( struct csr1212_keyval * kv )
{
if ( kv - > associate ) {
csr1212_release_keyval ( kv - > associate ) ;
}
kv - > associate = NULL ;
}
/* This function is used to free the memory taken by a keyval. If the given
* keyval is a directory type , then any keyvals contained in that directory
* will be destroyed as well if their respective refcnts are 0. By means of
* list manipulation , this routine will descend a directory structure in a
* non - recursive manner . */
void _csr1212_destroy_keyval ( struct csr1212_keyval * kv )
{
struct csr1212_keyval * k , * a ;
struct csr1212_dentry dentry ;
struct csr1212_dentry * head , * tail ;
dentry . kv = kv ;
dentry . next = NULL ;
dentry . prev = NULL ;
head = & dentry ;
tail = head ;
while ( head ) {
k = head - > kv ;
while ( k ) {
k - > refcnt - - ;
if ( k - > refcnt > 0 )
break ;
a = k - > associate ;
if ( k - > key . type = = CSR1212_KV_TYPE_DIRECTORY ) {
/* If the current entry is a directory, then move all
* the entries to the destruction list . */
if ( k - > value . directory . dentries_head ) {
tail - > next = k - > value . directory . dentries_head ;
k - > value . directory . dentries_head - > prev = tail ;
tail = k - > value . directory . dentries_tail ;
}
}
free_keyval ( k ) ;
k = a ;
}
head = head - > next ;
if ( head ) {
if ( head - > prev & & head - > prev ! = & dentry ) {
CSR1212_FREE ( head - > prev ) ;
}
head - > prev = NULL ;
} else if ( tail ! = & dentry )
CSR1212_FREE ( tail ) ;
}
}
void csr1212_destroy_csr ( struct csr1212_csr * csr )
{
struct csr1212_csr_rom_cache * c , * oc ;
struct csr1212_cache_region * cr , * ocr ;
csr1212_release_keyval ( csr - > root_kv ) ;
c = csr - > cache_head ;
while ( c ) {
oc = c ;
cr = c - > filled_head ;
while ( cr ) {
ocr = cr ;
cr = cr - > next ;
CSR1212_FREE ( ocr ) ;
}
c = c - > next ;
CSR1212_FREE ( oc ) ;
}
CSR1212_FREE ( csr ) ;
}
/* CSR Image Creation */
static int csr1212_append_new_cache ( struct csr1212_csr * csr , size_t romsize )
{
struct csr1212_csr_rom_cache * cache ;
u_int64_t csr_addr ;
2005-07-10 04:01:23 +04:00
if ( ! csr | | ! csr - > ops | | ! csr - > ops - > allocate_addr_range | |
! csr - > ops - > release_addr | | csr - > max_rom < 1 )
return CSR1212_EINVAL ;
2005-04-17 02:20:36 +04:00
/* ROM size must be a multiple of csr->max_rom */
romsize = ( romsize + ( csr - > max_rom - 1 ) ) & ~ ( csr - > max_rom - 1 ) ;
csr_addr = csr - > ops - > allocate_addr_range ( romsize , csr - > max_rom , csr - > private ) ;
if ( csr_addr = = ~ 0ULL ) {
return CSR1212_ENOMEM ;
}
if ( csr_addr < CSR1212_REGISTER_SPACE_BASE ) {
/* Invalid address returned from allocate_addr_range(). */
csr - > ops - > release_addr ( csr_addr , csr - > private ) ;
return CSR1212_ENOMEM ;
}
cache = csr1212_rom_cache_malloc ( csr_addr - CSR1212_REGISTER_SPACE_BASE , romsize ) ;
if ( ! cache ) {
csr - > ops - > release_addr ( csr_addr , csr - > private ) ;
return CSR1212_ENOMEM ;
}
cache - > ext_rom = csr1212_new_keyval ( CSR1212_KV_TYPE_LEAF , CSR1212_KV_ID_EXTENDED_ROM ) ;
if ( ! cache - > ext_rom ) {
csr - > ops - > release_addr ( csr_addr , csr - > private ) ;
CSR1212_FREE ( cache ) ;
return CSR1212_ENOMEM ;
}
if ( csr1212_attach_keyval_to_directory ( csr - > root_kv , cache - > ext_rom ) ! = CSR1212_SUCCESS ) {
csr1212_release_keyval ( cache - > ext_rom ) ;
csr - > ops - > release_addr ( csr_addr , csr - > private ) ;
CSR1212_FREE ( cache ) ;
return CSR1212_ENOMEM ;
}
cache - > ext_rom - > offset = csr_addr - CSR1212_REGISTER_SPACE_BASE ;
cache - > ext_rom - > value . leaf . len = - 1 ;
cache - > ext_rom - > value . leaf . data = cache - > data ;
/* Add cache to tail of cache list */
cache - > prev = csr - > cache_tail ;
csr - > cache_tail - > next = cache ;
csr - > cache_tail = cache ;
return CSR1212_SUCCESS ;
}
static inline void csr1212_remove_cache ( struct csr1212_csr * csr ,
struct csr1212_csr_rom_cache * cache )
{
if ( csr - > cache_head = = cache )
csr - > cache_head = cache - > next ;
if ( csr - > cache_tail = = cache )
csr - > cache_tail = cache - > prev ;
if ( cache - > prev )
cache - > prev - > next = cache - > next ;
if ( cache - > next )
cache - > next - > prev = cache - > prev ;
if ( cache - > ext_rom ) {
csr1212_detach_keyval_from_directory ( csr - > root_kv , cache - > ext_rom ) ;
csr1212_release_keyval ( cache - > ext_rom ) ;
}
CSR1212_FREE ( cache ) ;
}
static int csr1212_generate_layout_subdir ( struct csr1212_keyval * dir ,
struct csr1212_keyval * * layout_tail )
{
struct csr1212_dentry * dentry ;
struct csr1212_keyval * dkv ;
struct csr1212_keyval * last_extkey_spec = NULL ;
struct csr1212_keyval * last_extkey = NULL ;
int num_entries = 0 ;
for ( dentry = dir - > value . directory . dentries_head ; dentry ;
dentry = dentry - > next ) {
for ( dkv = dentry - > kv ; dkv ; dkv = dkv - > associate ) {
/* Special Case: Extended Key Specifier_ID */
if ( dkv - > key . id = = CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID ) {
if ( last_extkey_spec = = NULL ) {
last_extkey_spec = dkv ;
} else if ( dkv - > value . immediate ! = last_extkey_spec - > value . immediate ) {
last_extkey_spec = dkv ;
} else {
continue ;
}
/* Special Case: Extended Key */
} else if ( dkv - > key . id = = CSR1212_KV_ID_EXTENDED_KEY ) {
if ( last_extkey = = NULL ) {
last_extkey = dkv ;
} else if ( dkv - > value . immediate ! = last_extkey - > value . immediate ) {
last_extkey = dkv ;
} else {
continue ;
}
}
num_entries + = 1 ;
switch ( dkv - > key . type ) {
default :
case CSR1212_KV_TYPE_IMMEDIATE :
case CSR1212_KV_TYPE_CSR_OFFSET :
break ;
case CSR1212_KV_TYPE_LEAF :
case CSR1212_KV_TYPE_DIRECTORY :
/* Remove from list */
if ( dkv - > prev & & ( dkv - > prev - > next = = dkv ) )
dkv - > prev - > next = dkv - > next ;
if ( dkv - > next & & ( dkv - > next - > prev = = dkv ) )
dkv - > next - > prev = dkv - > prev ;
//if (dkv == *layout_tail)
// *layout_tail = dkv->prev;
/* Special case: Extended ROM leafs */
if ( dkv - > key . id = = CSR1212_KV_ID_EXTENDED_ROM ) {
dkv - > value . leaf . len = - 1 ;
/* Don't add Extended ROM leafs in the layout list,
* they are handled differently . */
break ;
}
/* Add to tail of list */
dkv - > next = NULL ;
dkv - > prev = * layout_tail ;
( * layout_tail ) - > next = dkv ;
* layout_tail = dkv ;
break ;
}
}
}
return num_entries ;
}
size_t csr1212_generate_layout_order ( struct csr1212_keyval * kv )
{
struct csr1212_keyval * ltail = kv ;
size_t agg_size = 0 ;
while ( kv ) {
switch ( kv - > key . type ) {
case CSR1212_KV_TYPE_LEAF :
/* Add 1 quadlet for crc/len field */
agg_size + = kv - > value . leaf . len + 1 ;
break ;
case CSR1212_KV_TYPE_DIRECTORY :
kv - > value . directory . len = csr1212_generate_layout_subdir ( kv , & ltail ) ;
/* Add 1 quadlet for crc/len field */
agg_size + = kv - > value . directory . len + 1 ;
break ;
}
kv = kv - > next ;
}
return quads_to_bytes ( agg_size ) ;
}
struct csr1212_keyval * csr1212_generate_positions ( struct csr1212_csr_rom_cache * cache ,
struct csr1212_keyval * start_kv ,
int start_pos )
{
struct csr1212_keyval * kv = start_kv ;
struct csr1212_keyval * okv = start_kv ;
int pos = start_pos ;
int kv_len = 0 , okv_len = 0 ;
cache - > layout_head = kv ;
while ( kv & & pos < cache - > size ) {
/* Special case: Extended ROM leafs */
if ( kv - > key . id ! = CSR1212_KV_ID_EXTENDED_ROM ) {
kv - > offset = cache - > offset + pos ;
}
switch ( kv - > key . type ) {
case CSR1212_KV_TYPE_LEAF :
kv_len = kv - > value . leaf . len ;
break ;
case CSR1212_KV_TYPE_DIRECTORY :
kv_len = kv - > value . directory . len ;
break ;
default :
/* Should never get here */
break ;
}
pos + = quads_to_bytes ( kv_len + 1 ) ;
if ( pos < = cache - > size ) {
okv = kv ;
okv_len = kv_len ;
kv = kv - > next ;
}
}
cache - > layout_tail = okv ;
cache - > len = ( okv - > offset - cache - > offset ) + quads_to_bytes ( okv_len + 1 ) ;
return kv ;
}
static void csr1212_generate_tree_subdir ( struct csr1212_keyval * dir ,
u_int32_t * data_buffer )
{
struct csr1212_dentry * dentry ;
struct csr1212_keyval * last_extkey_spec = NULL ;
struct csr1212_keyval * last_extkey = NULL ;
int index = 0 ;
for ( dentry = dir - > value . directory . dentries_head ; dentry ; dentry = dentry - > next ) {
struct csr1212_keyval * a ;
for ( a = dentry - > kv ; a ; a = a - > associate ) {
u_int32_t value = 0 ;
/* Special Case: Extended Key Specifier_ID */
if ( a - > key . id = = CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID ) {
if ( last_extkey_spec = = NULL ) {
last_extkey_spec = a ;
} else if ( a - > value . immediate ! = last_extkey_spec - > value . immediate ) {
last_extkey_spec = a ;
} else {
continue ;
}
/* Special Case: Extended Key */
} else if ( a - > key . id = = CSR1212_KV_ID_EXTENDED_KEY ) {
if ( last_extkey = = NULL ) {
last_extkey = a ;
} else if ( a - > value . immediate ! = last_extkey - > value . immediate ) {
last_extkey = a ;
} else {
continue ;
}
}
switch ( a - > key . type ) {
case CSR1212_KV_TYPE_IMMEDIATE :
value = a - > value . immediate ;
break ;
case CSR1212_KV_TYPE_CSR_OFFSET :
value = a - > value . csr_offset ;
break ;
case CSR1212_KV_TYPE_LEAF :
value = a - > offset ;
value - = dir - > offset + quads_to_bytes ( 1 + index ) ;
value = bytes_to_quads ( value ) ;
break ;
case CSR1212_KV_TYPE_DIRECTORY :
value = a - > offset ;
value - = dir - > offset + quads_to_bytes ( 1 + index ) ;
value = bytes_to_quads ( value ) ;
break ;
default :
/* Should never get here */
break ; /* GDB breakpoint */
}
value | = ( a - > key . id & CSR1212_KV_KEY_ID_MASK ) < < CSR1212_KV_KEY_SHIFT ;
value | = ( a - > key . type & CSR1212_KV_KEY_TYPE_MASK ) < <
( CSR1212_KV_KEY_SHIFT + CSR1212_KV_KEY_TYPE_SHIFT ) ;
data_buffer [ index ] = CSR1212_CPU_TO_BE32 ( value ) ;
index + + ;
}
}
}
void csr1212_fill_cache ( struct csr1212_csr_rom_cache * cache )
{
struct csr1212_keyval * kv , * nkv ;
struct csr1212_keyval_img * kvi ;
for ( kv = cache - > layout_head ; kv ! = cache - > layout_tail - > next ; kv = nkv ) {
kvi = ( struct csr1212_keyval_img * )
( cache - > data + bytes_to_quads ( kv - > offset - cache - > offset ) ) ;
switch ( kv - > key . type ) {
default :
case CSR1212_KV_TYPE_IMMEDIATE :
case CSR1212_KV_TYPE_CSR_OFFSET :
/* Should never get here */
break ; /* GDB breakpoint */
case CSR1212_KV_TYPE_LEAF :
/* Don't copy over Extended ROM areas, they are
* already filled out ! */
if ( kv - > key . id ! = CSR1212_KV_ID_EXTENDED_ROM )
memcpy ( kvi - > data , kv - > value . leaf . data ,
quads_to_bytes ( kv - > value . leaf . len ) ) ;
kvi - > length = CSR1212_CPU_TO_BE16 ( kv - > value . leaf . len ) ;
kvi - > crc = csr1212_crc16 ( kvi - > data , kv - > value . leaf . len ) ;
break ;
case CSR1212_KV_TYPE_DIRECTORY :
csr1212_generate_tree_subdir ( kv , kvi - > data ) ;
kvi - > length = CSR1212_CPU_TO_BE16 ( kv - > value . directory . len ) ;
kvi - > crc = csr1212_crc16 ( kvi - > data , kv - > value . directory . len ) ;
break ;
}
nkv = kv - > next ;
if ( kv - > prev )
kv - > prev - > next = NULL ;
if ( kv - > next )
kv - > next - > prev = NULL ;
kv - > prev = NULL ;
kv - > next = NULL ;
}
}
int csr1212_generate_csr_image ( struct csr1212_csr * csr )
{
struct csr1212_bus_info_block_img * bi ;
struct csr1212_csr_rom_cache * cache ;
struct csr1212_keyval * kv ;
size_t agg_size ;
int ret ;
int init_offset ;
if ( ! csr )
return CSR1212_EINVAL ;
cache = csr - > cache_head ;
bi = ( struct csr1212_bus_info_block_img * ) cache - > data ;
bi - > length = bytes_to_quads ( csr - > bus_info_len ) - 1 ;
bi - > crc_length = bi - > length ;
bi - > crc = csr1212_crc16 ( bi - > data , bi - > crc_length ) ;
csr - > root_kv - > next = NULL ;
csr - > root_kv - > prev = NULL ;
agg_size = csr1212_generate_layout_order ( csr - > root_kv ) ;
init_offset = csr - > bus_info_len ;
for ( kv = csr - > root_kv , cache = csr - > cache_head ; kv ; cache = cache - > next ) {
if ( ! cache ) {
/* Estimate approximate number of additional cache
* regions needed ( it assumes that the cache holding
* the first 1 K Config ROM space always exists ) . */
int est_c = agg_size / ( CSR1212_EXTENDED_ROM_SIZE -
( 2 * sizeof ( u_int32_t ) ) ) + 1 ;
/* Add additional cache regions, extras will be
* removed later */
for ( ; est_c ; est_c - - ) {
ret = csr1212_append_new_cache ( csr , CSR1212_EXTENDED_ROM_SIZE ) ;
if ( ret ! = CSR1212_SUCCESS )
return ret ;
}
/* Need to re-layout for additional cache regions */
agg_size = csr1212_generate_layout_order ( csr - > root_kv ) ;
kv = csr - > root_kv ;
cache = csr - > cache_head ;
init_offset = csr - > bus_info_len ;
}
kv = csr1212_generate_positions ( cache , kv , init_offset ) ;
agg_size - = cache - > len ;
init_offset = sizeof ( u_int32_t ) ;
}
/* Remove unused, excess cache regions */
while ( cache ) {
struct csr1212_csr_rom_cache * oc = cache ;
cache = cache - > next ;
csr1212_remove_cache ( csr , oc ) ;
}
/* Go through the list backward so that when done, the correct CRC
* will be calculated for the Extended ROM areas . */
for ( cache = csr - > cache_tail ; cache ; cache = cache - > prev ) {
/* Only Extended ROM caches should have this set. */
if ( cache - > ext_rom ) {
int leaf_size ;
/* Make sure the Extended ROM leaf is a multiple of
* max_rom in size . */
2005-07-10 04:01:23 +04:00
if ( csr - > max_rom < 1 )
return CSR1212_EINVAL ;
2005-04-17 02:20:36 +04:00
leaf_size = ( cache - > len + ( csr - > max_rom - 1 ) ) &
~ ( csr - > max_rom - 1 ) ;
/* Zero out the unused ROM region */
memset ( cache - > data + bytes_to_quads ( cache - > len ) , 0x00 ,
leaf_size - cache - > len ) ;
/* Subtract leaf header */
leaf_size - = sizeof ( u_int32_t ) ;
/* Update the Extended ROM leaf length */
cache - > ext_rom - > value . leaf . len =
bytes_to_quads ( leaf_size ) ;
} else {
/* Zero out the unused ROM region */
memset ( cache - > data + bytes_to_quads ( cache - > len ) , 0x00 ,
cache - > size - cache - > len ) ;
}
/* Copy the data into the cache buffer */
csr1212_fill_cache ( cache ) ;
if ( cache ! = csr - > cache_head ) {
/* Set the length and CRC of the extended ROM. */
struct csr1212_keyval_img * kvi =
( struct csr1212_keyval_img * ) cache - > data ;
kvi - > length = CSR1212_CPU_TO_BE16 ( bytes_to_quads ( cache - > len ) - 1 ) ;
kvi - > crc = csr1212_crc16 ( kvi - > data ,
bytes_to_quads ( cache - > len ) - 1 ) ;
}
}
return CSR1212_SUCCESS ;
}
int csr1212_read ( struct csr1212_csr * csr , u_int32_t offset , void * buffer , u_int32_t len )
{
struct csr1212_csr_rom_cache * cache ;
for ( cache = csr - > cache_head ; cache ; cache = cache - > next ) {
if ( offset > = cache - > offset & &
( offset + len ) < = ( cache - > offset + cache - > size ) ) {
memcpy ( buffer ,
& cache - > data [ bytes_to_quads ( offset - cache - > offset ) ] ,
len ) ;
return CSR1212_SUCCESS ;
}
}
return CSR1212_ENOENT ;
}
/* Parse a chunk of data as a Config ROM */
static int csr1212_parse_bus_info_block ( struct csr1212_csr * csr )
{
struct csr1212_bus_info_block_img * bi ;
struct csr1212_cache_region * cr ;
int i ;
int ret ;
/* IEEE 1212 says that the entire bus info block should be readable in
* a single transaction regardless of the max_rom value .
* Unfortunately , many IEEE 1394 devices do not abide by that , so the
* bus info block will be read 1 quadlet at a time . The rest of the
* ConfigROM will be read according to the max_rom field . */
for ( i = 0 ; i < csr - > bus_info_len ; i + = sizeof ( csr1212_quad_t ) ) {
ret = csr - > ops - > bus_read ( csr , CSR1212_CONFIG_ROM_SPACE_BASE + i ,
sizeof ( csr1212_quad_t ) ,
& csr - > cache_head - > data [ bytes_to_quads ( i ) ] ,
csr - > private ) ;
if ( ret ! = CSR1212_SUCCESS )
return ret ;
}
bi = ( struct csr1212_bus_info_block_img * ) csr - > cache_head - > data ;
csr - > crc_len = quads_to_bytes ( bi - > crc_length ) ;
/* IEEE 1212 recommends that crc_len be equal to bus_info_len, but that is not
* always the case , so read the rest of the crc area 1 quadlet at a time . */
for ( i = csr - > bus_info_len ; i < = csr - > crc_len ; i + = sizeof ( csr1212_quad_t ) ) {
ret = csr - > ops - > bus_read ( csr , CSR1212_CONFIG_ROM_SPACE_BASE + i ,
sizeof ( csr1212_quad_t ) ,
& csr - > cache_head - > data [ bytes_to_quads ( i ) ] ,
csr - > private ) ;
if ( ret ! = CSR1212_SUCCESS )
return ret ;
}
if ( bytes_to_quads ( csr - > bus_info_len - sizeof ( csr1212_quad_t ) ) ! = bi - > length )
return CSR1212_EINVAL ;
#if 0
/* Apparently there are too many differnt wrong implementations of the
* CRC algorithm that verifying them is moot . */
if ( ( csr1212_crc16 ( bi - > data , bi - > crc_length ) ! = bi - > crc ) & &
( csr1212_msft_crc16 ( bi - > data , bi - > crc_length ) ! = bi - > crc ) )
return CSR1212_EINVAL ;
# endif
cr = CSR1212_MALLOC ( sizeof ( struct csr1212_cache_region ) ) ;
if ( ! cr )
return CSR1212_ENOMEM ;
cr - > next = NULL ;
cr - > prev = NULL ;
cr - > offset_start = 0 ;
cr - > offset_end = csr - > crc_len + 4 ;
csr - > cache_head - > filled_head = cr ;
csr - > cache_head - > filled_tail = cr ;
return CSR1212_SUCCESS ;
}
static int csr1212_parse_dir_entry ( struct csr1212_keyval * dir ,
csr1212_quad_t ki ,
u_int32_t kv_pos )
{
int ret = CSR1212_SUCCESS ;
struct csr1212_keyval * k = NULL ;
u_int32_t offset ;
switch ( CSR1212_KV_KEY_TYPE ( ki ) ) {
case CSR1212_KV_TYPE_IMMEDIATE :
k = csr1212_new_immediate ( CSR1212_KV_KEY_ID ( ki ) ,
CSR1212_KV_VAL ( ki ) ) ;
if ( ! k ) {
ret = CSR1212_ENOMEM ;
goto fail ;
}
k - > refcnt = 0 ; /* Don't keep local reference when parsing. */
break ;
case CSR1212_KV_TYPE_CSR_OFFSET :
k = csr1212_new_csr_offset ( CSR1212_KV_KEY_ID ( ki ) ,
CSR1212_KV_VAL ( ki ) ) ;
if ( ! k ) {
ret = CSR1212_ENOMEM ;
goto fail ;
}
k - > refcnt = 0 ; /* Don't keep local reference when parsing. */
break ;
default :
/* Compute the offset from 0xffff f000 0000. */
offset = quads_to_bytes ( CSR1212_KV_VAL ( ki ) ) + kv_pos ;
if ( offset = = kv_pos ) {
/* Uh-oh. Can't have a relative offset of 0 for Leaves
* or Directories . The Config ROM image is most likely
* messed up , so we ' ll just abort here . */
ret = CSR1212_EIO ;
goto fail ;
}
k = csr1212_find_keyval_offset ( dir , offset ) ;
if ( k )
break ; /* Found it. */
if ( CSR1212_KV_KEY_TYPE ( ki ) = = CSR1212_KV_TYPE_DIRECTORY ) {
k = csr1212_new_directory ( CSR1212_KV_KEY_ID ( ki ) ) ;
} else {
k = csr1212_new_leaf ( CSR1212_KV_KEY_ID ( ki ) , NULL , 0 ) ;
}
if ( ! k ) {
ret = CSR1212_ENOMEM ;
goto fail ;
}
k - > refcnt = 0 ; /* Don't keep local reference when parsing. */
k - > valid = 0 ; /* Contents not read yet so it's not valid. */
k - > offset = offset ;
k - > prev = dir ;
k - > next = dir - > next ;
dir - > next - > prev = k ;
dir - > next = k ;
}
ret = csr1212_attach_keyval_to_directory ( dir , k ) ;
fail :
if ( ret ! = CSR1212_SUCCESS ) {
if ( k )
free_keyval ( k ) ;
}
return ret ;
}
int csr1212_parse_keyval ( struct csr1212_keyval * kv ,
struct csr1212_csr_rom_cache * cache )
{
struct csr1212_keyval_img * kvi ;
int i ;
int ret = CSR1212_SUCCESS ;
int kvi_len ;
kvi = ( struct csr1212_keyval_img * ) & cache - > data [ bytes_to_quads ( kv - > offset -
cache - > offset ) ] ;
kvi_len = CSR1212_BE16_TO_CPU ( kvi - > length ) ;
#if 0
/* Apparently there are too many differnt wrong implementations of the
* CRC algorithm that verifying them is moot . */
if ( ( csr1212_crc16 ( kvi - > data , kvi_len ) ! = kvi - > crc ) & &
( csr1212_msft_crc16 ( kvi - > data , kvi_len ) ! = kvi - > crc ) ) {
ret = CSR1212_EINVAL ;
goto fail ;
}
# endif
switch ( kv - > key . type ) {
case CSR1212_KV_TYPE_DIRECTORY :
for ( i = 0 ; i < kvi_len ; i + + ) {
csr1212_quad_t ki = kvi - > data [ i ] ;
/* Some devices put null entries in their unit
* directories . If we come across such an entry ,
* then skip it . */
if ( ki = = 0x0 )
continue ;
ret = csr1212_parse_dir_entry ( kv , ki ,
( kv - > offset +
quads_to_bytes ( i + 1 ) ) ) ;
}
kv - > value . directory . len = kvi_len ;
break ;
case CSR1212_KV_TYPE_LEAF :
if ( kv - > key . id ! = CSR1212_KV_ID_EXTENDED_ROM ) {
kv - > value . leaf . data = CSR1212_MALLOC ( quads_to_bytes ( kvi_len ) ) ;
if ( ! kv - > value . leaf . data )
{
ret = CSR1212_ENOMEM ;
goto fail ;
}
kv - > value . leaf . len = kvi_len ;
memcpy ( kv - > value . leaf . data , kvi - > data , quads_to_bytes ( kvi_len ) ) ;
}
break ;
}
kv - > valid = 1 ;
fail :
return ret ;
}
int _csr1212_read_keyval ( struct csr1212_csr * csr , struct csr1212_keyval * kv )
{
struct csr1212_cache_region * cr , * ncr , * newcr = NULL ;
struct csr1212_keyval_img * kvi = NULL ;
struct csr1212_csr_rom_cache * cache ;
int cache_index ;
u_int64_t addr ;
u_int32_t * cache_ptr ;
u_int16_t kv_len = 0 ;
2005-07-10 04:01:23 +04:00
if ( ! csr | | ! kv | | csr - > max_rom < 1 )
2005-04-17 02:20:36 +04:00
return CSR1212_EINVAL ;
/* First find which cache the data should be in (or go in if not read
* yet ) . */
for ( cache = csr - > cache_head ; cache ; cache = cache - > next ) {
if ( kv - > offset > = cache - > offset & &
kv - > offset < ( cache - > offset + cache - > size ) )
break ;
}
if ( ! cache ) {
csr1212_quad_t q ;
u_int32_t cache_size ;
/* Only create a new cache for Extended ROM leaves. */
if ( kv - > key . id ! = CSR1212_KV_ID_EXTENDED_ROM )
return CSR1212_EINVAL ;
if ( csr - > ops - > bus_read ( csr ,
CSR1212_REGISTER_SPACE_BASE + kv - > offset ,
sizeof ( csr1212_quad_t ) , & q , csr - > private ) ) {
return CSR1212_EIO ;
}
kv - > value . leaf . len = CSR1212_BE32_TO_CPU ( q ) > > 16 ;
cache_size = ( quads_to_bytes ( kv - > value . leaf . len + 1 ) +
( csr - > max_rom - 1 ) ) & ~ ( csr - > max_rom - 1 ) ;
cache = csr1212_rom_cache_malloc ( kv - > offset , cache_size ) ;
if ( ! cache )
return CSR1212_ENOMEM ;
kv - > value . leaf . data = & cache - > data [ 1 ] ;
csr - > cache_tail - > next = cache ;
cache - > prev = csr - > cache_tail ;
cache - > next = NULL ;
csr - > cache_tail = cache ;
cache - > filled_head =
CSR1212_MALLOC ( sizeof ( struct csr1212_cache_region ) ) ;
if ( ! cache - > filled_head ) {
return CSR1212_ENOMEM ;
}
cache - > filled_head - > offset_start = 0 ;
cache - > filled_head - > offset_end = sizeof ( csr1212_quad_t ) ;
cache - > filled_tail = cache - > filled_head ;
cache - > filled_head - > next = NULL ;
cache - > filled_head - > prev = NULL ;
cache - > data [ 0 ] = q ;
/* Don't read the entire extended ROM now. Pieces of it will
* be read when entries inside it are read . */
return csr1212_parse_keyval ( kv , cache ) ;
}
cache_index = kv - > offset - cache - > offset ;
/* Now seach read portions of the cache to see if it is there. */
for ( cr = cache - > filled_head ; cr ; cr = cr - > next ) {
if ( cache_index < cr - > offset_start ) {
newcr = CSR1212_MALLOC ( sizeof ( struct csr1212_cache_region ) ) ;
if ( ! newcr )
return CSR1212_ENOMEM ;
newcr - > offset_start = cache_index & ~ ( csr - > max_rom - 1 ) ;
newcr - > offset_end = newcr - > offset_start ;
newcr - > next = cr ;
newcr - > prev = cr - > prev ;
cr - > prev = newcr ;
cr = newcr ;
break ;
} else if ( ( cache_index > = cr - > offset_start ) & &
( cache_index < cr - > offset_end ) ) {
kvi = ( struct csr1212_keyval_img * )
( & cache - > data [ bytes_to_quads ( cache_index ) ] ) ;
kv_len = quads_to_bytes ( CSR1212_BE16_TO_CPU ( kvi - > length ) +
1 ) ;
break ;
} else if ( cache_index = = cr - > offset_end )
break ;
}
if ( ! cr ) {
cr = cache - > filled_tail ;
newcr = CSR1212_MALLOC ( sizeof ( struct csr1212_cache_region ) ) ;
if ( ! newcr )
return CSR1212_ENOMEM ;
newcr - > offset_start = cache_index & ~ ( csr - > max_rom - 1 ) ;
newcr - > offset_end = newcr - > offset_start ;
newcr - > prev = cr ;
newcr - > next = cr - > next ;
cr - > next = newcr ;
cr = newcr ;
cache - > filled_tail = newcr ;
}
while ( ! kvi | | cr - > offset_end < cache_index + kv_len ) {
cache_ptr = & cache - > data [ bytes_to_quads ( cr - > offset_end &
~ ( csr - > max_rom - 1 ) ) ] ;
addr = ( CSR1212_CSR_ARCH_REG_SPACE_BASE + cache - > offset +
cr - > offset_end ) & ~ ( csr - > max_rom - 1 ) ;
if ( csr - > ops - > bus_read ( csr , addr , csr - > max_rom , cache_ptr ,
csr - > private ) ) {
if ( csr - > max_rom = = 4 )
/* We've got problems! */
return CSR1212_EIO ;
/* Apperently the max_rom value was a lie, set it to
* do quadlet reads and try again . */
csr - > max_rom = 4 ;
continue ;
}
cr - > offset_end + = csr - > max_rom - ( cr - > offset_end &
( csr - > max_rom - 1 ) ) ;
if ( ! kvi & & ( cr - > offset_end > cache_index ) ) {
kvi = ( struct csr1212_keyval_img * )
( & cache - > data [ bytes_to_quads ( cache_index ) ] ) ;
kv_len = quads_to_bytes ( CSR1212_BE16_TO_CPU ( kvi - > length ) +
1 ) ;
}
if ( ( kv_len + ( kv - > offset - cache - > offset ) ) > cache - > size ) {
/* The Leaf or Directory claims its length extends
* beyond the ConfigROM image region and thus beyond the
* end of our cache region . Therefore , we abort now
* rather than seg faulting later . */
return CSR1212_EIO ;
}
ncr = cr - > next ;
if ( ncr & & ( cr - > offset_end > = ncr - > offset_start ) ) {
/* consolidate region entries */
ncr - > offset_start = cr - > offset_start ;
if ( cr - > prev )
cr - > prev - > next = cr - > next ;
ncr - > prev = cr - > prev ;
if ( cache - > filled_head = = cr )
cache - > filled_head = ncr ;
CSR1212_FREE ( cr ) ;
cr = ncr ;
}
}
return csr1212_parse_keyval ( kv , cache ) ;
}
int csr1212_parse_csr ( struct csr1212_csr * csr )
{
static const int mr_map [ ] = { 4 , 64 , 1024 , 0 } ;
struct csr1212_dentry * dentry ;
int ret ;
2005-07-10 04:01:23 +04:00
if ( ! csr | | ! csr - > ops | | ! csr - > ops - > bus_read )
2005-04-17 02:20:36 +04:00
return CSR1212_EINVAL ;
ret = csr1212_parse_bus_info_block ( csr ) ;
if ( ret ! = CSR1212_SUCCESS )
return ret ;
if ( ! csr - > ops - > get_max_rom )
csr - > max_rom = mr_map [ 0 ] ; /* default value */
2005-07-10 04:01:23 +04:00
else {
int i = csr - > ops - > get_max_rom ( csr - > bus_info_data ,
csr - > private ) ;
if ( i & ~ 0x3 )
return CSR1212_EINVAL ;
csr - > max_rom = mr_map [ i ] ;
}
2005-04-17 02:20:36 +04:00
csr - > cache_head - > layout_head = csr - > root_kv ;
csr - > cache_head - > layout_tail = csr - > root_kv ;
csr - > root_kv - > offset = ( CSR1212_CONFIG_ROM_SPACE_BASE & 0xffff ) +
csr - > bus_info_len ;
csr - > root_kv - > valid = 0 ;
csr - > root_kv - > next = csr - > root_kv ;
csr - > root_kv - > prev = csr - > root_kv ;
csr1212_get_keyval ( csr , csr - > root_kv ) ;
/* Scan through the Root directory finding all extended ROM regions
* and make cache regions for them */
for ( dentry = csr - > root_kv - > value . directory . dentries_head ;
dentry ; dentry = dentry - > next ) {
if ( dentry - > kv - > key . id = = CSR1212_KV_ID_EXTENDED_ROM ) {
csr1212_get_keyval ( csr , dentry - > kv ) ;
if ( ret ! = CSR1212_SUCCESS )
return ret ;
}
}
return CSR1212_SUCCESS ;
}