2005-04-17 02:20:36 +04:00
/*
* Compaq Hot Plug Controller Driver
*
* Copyright ( C ) 1995 , 2001 Compaq Computer Corporation
* Copyright ( C ) 2001 Greg Kroah - Hartman ( greg @ kroah . com )
* Copyright ( C ) 2001 IBM Corp .
*
* All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or ( at
* your option ) any later version .
*
* 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 , GOOD TITLE or
* NON INFRINGEMENT . 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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
* Send feedback to < greg @ kroah . com >
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/proc_fs.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# include <linux/pci.h>
2006-10-14 07:05:19 +04:00
# include <linux/pci_hotplug.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <asm/uaccess.h>
# include "cpqphp.h"
# include "cpqphp_nvram.h"
# define ROM_INT15_PHY_ADDR 0x0FF859
# define READ_EV 0xD8A4
# define WRITE_EV 0xD8A5
struct register_foo {
union {
unsigned long lword ; /* eax */
unsigned short word ; /* ax */
struct {
unsigned char low ; /* al */
unsigned char high ; /* ah */
} byte ;
} data ;
unsigned char opcode ; /* see below */
unsigned long length ; /* if the reg. is a pointer, how much data */
} __attribute__ ( ( packed ) ) ;
struct all_reg {
struct register_foo eax_reg ;
struct register_foo ebx_reg ;
struct register_foo ecx_reg ;
struct register_foo edx_reg ;
struct register_foo edi_reg ;
struct register_foo esi_reg ;
struct register_foo eflags_reg ;
} __attribute__ ( ( packed ) ) ;
struct ev_hrt_header {
u8 Version ;
u8 num_of_ctrl ;
u8 next ;
} ;
struct ev_hrt_ctrl {
u8 bus ;
u8 device ;
u8 function ;
u8 mem_avail ;
u8 p_mem_avail ;
u8 io_avail ;
u8 bus_avail ;
u8 next ;
} ;
static u8 evbuffer_init ;
static u8 evbuffer_length ;
static u8 evbuffer [ 1024 ] ;
static void __iomem * compaq_int15_entry_point ;
2009-03-31 19:23:16 +04:00
/* lock for ordering int15_bios_call() */
static spinlock_t int15_lock ;
2005-04-17 02:20:36 +04:00
/* This is a series of function that deals with
2009-03-31 19:23:16 +04:00
* setting & getting the hotplug resource table in some environment variable .
*/
2005-04-17 02:20:36 +04:00
/*
* We really shouldn ' t be doing this unless there is a _very_ good reason to ! ! !
* greg k - h
*/
static u32 add_byte ( u32 * * p_buffer , u8 value , u32 * used , u32 * avail )
{
u8 * * tByte ;
if ( ( * used + 1 ) > * avail )
return ( 1 ) ;
2009-03-31 19:23:11 +04:00
2005-04-17 02:20:36 +04:00
* ( ( u8 * ) * p_buffer ) = value ;
tByte = ( u8 * * ) p_buffer ;
( * tByte ) + + ;
* used + = 1 ;
return ( 0 ) ;
}
static u32 add_dword ( u32 * * p_buffer , u32 value , u32 * used , u32 * avail )
{
if ( ( * used + 4 ) > * avail )
return ( 1 ) ;
* * p_buffer = value ;
( * p_buffer ) + + ;
* used + = 4 ;
return ( 0 ) ;
}
/*
* check_for_compaq_ROM
*
* this routine verifies that the ROM OEM string is ' COMPAQ '
*
* returns 0 for non - Compaq ROM , 1 for Compaq ROM
*/
static int check_for_compaq_ROM ( void __iomem * rom_start )
{
u8 temp1 , temp2 , temp3 , temp4 , temp5 , temp6 ;
int result = 0 ;
temp1 = readb ( rom_start + 0xffea + 0 ) ;
temp2 = readb ( rom_start + 0xffea + 1 ) ;
temp3 = readb ( rom_start + 0xffea + 2 ) ;
temp4 = readb ( rom_start + 0xffea + 3 ) ;
temp5 = readb ( rom_start + 0xffea + 4 ) ;
temp6 = readb ( rom_start + 0xffea + 5 ) ;
if ( ( temp1 = = ' C ' ) & &
( temp2 = = ' O ' ) & &
( temp3 = = ' M ' ) & &
( temp4 = = ' P ' ) & &
( temp5 = = ' A ' ) & &
( temp6 = = ' Q ' ) ) {
result = 1 ;
}
2008-03-04 06:09:46 +03:00
dbg ( " %s - returned %d \n " , __func__ , result ) ;
2005-04-17 02:20:36 +04:00
return result ;
}
static u32 access_EV ( u16 operation , u8 * ev_name , u8 * buffer , u32 * buf_size )
{
unsigned long flags ;
int op = operation ;
int ret_val ;
2009-03-31 19:23:11 +04:00
2005-04-17 02:20:36 +04:00
if ( ! compaq_int15_entry_point )
return - ENODEV ;
2009-03-31 19:23:11 +04:00
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & int15_lock , flags ) ;
__asm__ (
" xorl %%ebx,%%ebx \n " \
" xorl %%edx,%%edx \n " \
" pushf \n " \
" push %%cs \n " \
" cli \n " \
" call *%6 \n "
: " =c " ( * buf_size ) , " =a " ( ret_val )
: " a " ( op ) , " c " ( * buf_size ) , " S " ( ev_name ) ,
" D " ( buffer ) , " m " ( compaq_int15_entry_point )
: " %ebx " , " %edx " ) ;
spin_unlock_irqrestore ( & int15_lock , flags ) ;
2009-03-31 19:23:11 +04:00
2005-04-17 02:20:36 +04:00
return ( ( ret_val & 0xFF00 ) > > 8 ) ;
}
/*
* load_HRT
*
* Read the hot plug Resource Table from NVRAM
*/
static int load_HRT ( void __iomem * rom_start )
{
u32 available ;
u32 temp_dword ;
u8 temp_byte = 0xFF ;
u32 rc ;
if ( ! check_for_compaq_ROM ( rom_start ) ) {
return - ENODEV ;
}
available = 1024 ;
2009-03-31 19:23:16 +04:00
/* Now load the EV */
2005-04-17 02:20:36 +04:00
temp_dword = available ;
rc = access_EV ( READ_EV , " CQTHPS " , evbuffer , & temp_dword ) ;
evbuffer_length = temp_dword ;
2009-03-31 19:23:16 +04:00
/* We're maintaining the resource lists so write FF to invalidate old
* info
*/
2005-04-17 02:20:36 +04:00
temp_dword = 1 ;
rc = access_EV ( WRITE_EV , " CQTHPS " , & temp_byte , & temp_dword ) ;
return rc ;
}
/*
* store_HRT
*
* Save the hot plug Resource Table in NVRAM
*/
static u32 store_HRT ( void __iomem * rom_start )
{
u32 * buffer ;
u32 * pFill ;
u32 usedbytes ;
u32 available ;
u32 temp_dword ;
u32 rc ;
u8 loop ;
u8 numCtrl = 0 ;
struct controller * ctrl ;
struct pci_resource * resNode ;
struct ev_hrt_header * p_EV_header ;
struct ev_hrt_ctrl * p_ev_ctrl ;
available = 1024 ;
if ( ! check_for_compaq_ROM ( rom_start ) ) {
return ( 1 ) ;
}
buffer = ( u32 * ) evbuffer ;
if ( ! buffer )
return ( 1 ) ;
pFill = buffer ;
usedbytes = 0 ;
p_EV_header = ( struct ev_hrt_header * ) pFill ;
ctrl = cpqhp_ctrl_list ;
2009-03-31 19:23:11 +04:00
2009-03-31 19:23:16 +04:00
/* The revision of this structure */
2005-04-17 02:20:36 +04:00
rc = add_byte ( & pFill , 1 + ctrl - > push_flag , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* The number of controllers */
2005-04-17 02:20:36 +04:00
rc = add_byte ( & pFill , 1 , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
while ( ctrl ) {
p_ev_ctrl = ( struct ev_hrt_ctrl * ) pFill ;
numCtrl + + ;
2009-03-31 19:23:16 +04:00
/* The bus number */
2005-04-17 02:20:36 +04:00
rc = add_byte ( & pFill , ctrl - > bus , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* The device Number */
2005-04-17 02:20:36 +04:00
rc = add_byte ( & pFill , PCI_SLOT ( ctrl - > pci_dev - > devfn ) , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* The function Number */
2005-04-17 02:20:36 +04:00
rc = add_byte ( & pFill , PCI_FUNC ( ctrl - > pci_dev - > devfn ) , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* Skip the number of available entries */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , 0 , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* Figure out memory Available */
2005-04-17 02:20:36 +04:00
resNode = ctrl - > mem_head ;
loop = 0 ;
while ( resNode ) {
loop + + ;
2009-03-31 19:23:16 +04:00
/* base */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > base , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* length */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > length , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
resNode = resNode - > next ;
}
2009-03-31 19:23:16 +04:00
/* Fill in the number of entries */
2005-04-17 02:20:36 +04:00
p_ev_ctrl - > mem_avail = loop ;
2009-03-31 19:23:16 +04:00
/* Figure out prefetchable memory Available */
2005-04-17 02:20:36 +04:00
resNode = ctrl - > p_mem_head ;
loop = 0 ;
while ( resNode ) {
loop + + ;
2009-03-31 19:23:16 +04:00
/* base */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > base , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* length */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > length , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
resNode = resNode - > next ;
}
2009-03-31 19:23:16 +04:00
/* Fill in the number of entries */
2005-04-17 02:20:36 +04:00
p_ev_ctrl - > p_mem_avail = loop ;
2009-03-31 19:23:16 +04:00
/* Figure out IO Available */
2005-04-17 02:20:36 +04:00
resNode = ctrl - > io_head ;
loop = 0 ;
while ( resNode ) {
loop + + ;
2009-03-31 19:23:16 +04:00
/* base */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > base , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* length */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > length , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
resNode = resNode - > next ;
}
2009-03-31 19:23:16 +04:00
/* Fill in the number of entries */
2005-04-17 02:20:36 +04:00
p_ev_ctrl - > io_avail = loop ;
2009-03-31 19:23:16 +04:00
/* Figure out bus Available */
2005-04-17 02:20:36 +04:00
resNode = ctrl - > bus_head ;
loop = 0 ;
while ( resNode ) {
loop + + ;
2009-03-31 19:23:16 +04:00
/* base */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > base , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
2009-03-31 19:23:16 +04:00
/* length */
2005-04-17 02:20:36 +04:00
rc = add_dword ( & pFill , resNode - > length , & usedbytes , & available ) ;
if ( rc )
return ( rc ) ;
resNode = resNode - > next ;
}
2009-03-31 19:23:16 +04:00
/* Fill in the number of entries */
2005-04-17 02:20:36 +04:00
p_ev_ctrl - > bus_avail = loop ;
ctrl = ctrl - > next ;
}
2009-03-31 19:23:11 +04:00
2005-04-17 02:20:36 +04:00
p_EV_header - > num_of_ctrl = numCtrl ;
2009-03-31 19:23:16 +04:00
/* Now store the EV */
2005-04-17 02:20:36 +04:00
temp_dword = usedbytes ;
rc = access_EV ( WRITE_EV , " CQTHPS " , ( u8 * ) buffer , & temp_dword ) ;
dbg ( " usedbytes = 0x%x, length = 0x%x \n " , usedbytes , temp_dword ) ;
evbuffer_length = temp_dword ;
if ( rc ) {
err ( msg_unable_to_save ) ;
return ( 1 ) ;
}
return ( 0 ) ;
}
void compaq_nvram_init ( void __iomem * rom_start )
{
if ( rom_start ) {
compaq_int15_entry_point = ( rom_start + ROM_INT15_PHY_ADDR - ROM_PHY_ADDR ) ;
}
dbg ( " int15 entry = %p \n " , compaq_int15_entry_point ) ;
/* initialize our int15 lock */
spin_lock_init ( & int15_lock ) ;
}
int compaq_nvram_load ( void __iomem * rom_start , struct controller * ctrl )
{
u8 bus , device , function ;
u8 nummem , numpmem , numio , numbus ;
u32 rc ;
u8 * p_byte ;
struct pci_resource * mem_node ;
struct pci_resource * p_mem_node ;
struct pci_resource * io_node ;
struct pci_resource * bus_node ;
struct ev_hrt_ctrl * p_ev_ctrl ;
struct ev_hrt_header * p_EV_header ;
if ( ! evbuffer_init ) {
2009-03-31 19:23:16 +04:00
/* Read the resource list information in from NVRAM */
2005-04-17 02:20:36 +04:00
if ( load_HRT ( rom_start ) )
memset ( evbuffer , 0 , 1024 ) ;
evbuffer_init = 1 ;
}
2009-03-31 19:23:16 +04:00
/* If we saved information in NVRAM, use it now */
2005-04-17 02:20:36 +04:00
p_EV_header = ( struct ev_hrt_header * ) evbuffer ;
2009-03-31 19:23:16 +04:00
/* The following code is for systems where version 1.0 of this
* driver has been loaded , but doesn ' t support the hardware .
* In that case , the driver would incorrectly store something
* in NVRAM .
*/
2005-04-17 02:20:36 +04:00
if ( ( p_EV_header - > Version = = 2 ) | |
( ( p_EV_header - > Version = = 1 ) & & ! ctrl - > push_flag ) ) {
p_byte = & ( p_EV_header - > next ) ;
p_ev_ctrl = ( struct ev_hrt_ctrl * ) & ( p_EV_header - > next ) ;
p_byte + = 3 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) )
return 2 ;
bus = p_ev_ctrl - > bus ;
device = p_ev_ctrl - > device ;
function = p_ev_ctrl - > function ;
while ( ( bus ! = ctrl - > bus ) | |
2009-03-31 19:23:11 +04:00
( device ! = PCI_SLOT ( ctrl - > pci_dev - > devfn ) ) | |
2005-04-17 02:20:36 +04:00
( function ! = PCI_FUNC ( ctrl - > pci_dev - > devfn ) ) ) {
nummem = p_ev_ctrl - > mem_avail ;
numpmem = p_ev_ctrl - > p_mem_avail ;
numio = p_ev_ctrl - > io_avail ;
numbus = p_ev_ctrl - > bus_avail ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) )
return 2 ;
2009-03-31 19:23:16 +04:00
/* Skip forward to the next entry */
2005-04-17 02:20:36 +04:00
p_byte + = ( nummem + numpmem + numio + numbus ) * 8 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) )
return 2 ;
p_ev_ctrl = ( struct ev_hrt_ctrl * ) p_byte ;
p_byte + = 3 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) )
return 2 ;
bus = p_ev_ctrl - > bus ;
device = p_ev_ctrl - > device ;
function = p_ev_ctrl - > function ;
}
nummem = p_ev_ctrl - > mem_avail ;
numpmem = p_ev_ctrl - > p_mem_avail ;
numio = p_ev_ctrl - > io_avail ;
numbus = p_ev_ctrl - > bus_avail ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) )
return 2 ;
while ( nummem - - ) {
2006-12-13 11:35:56 +03:00
mem_node = kmalloc ( sizeof ( struct pci_resource ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! mem_node )
break ;
mem_node - > base = * ( u32 * ) p_byte ;
dbg ( " mem base = %8.8x \n " , mem_node - > base ) ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( mem_node ) ;
return 2 ;
}
mem_node - > length = * ( u32 * ) p_byte ;
dbg ( " mem length = %8.8x \n " , mem_node - > length ) ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( mem_node ) ;
return 2 ;
}
mem_node - > next = ctrl - > mem_head ;
ctrl - > mem_head = mem_node ;
}
while ( numpmem - - ) {
2006-12-13 11:35:56 +03:00
p_mem_node = kmalloc ( sizeof ( struct pci_resource ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! p_mem_node )
break ;
p_mem_node - > base = * ( u32 * ) p_byte ;
dbg ( " pre-mem base = %8.8x \n " , p_mem_node - > base ) ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( p_mem_node ) ;
return 2 ;
}
p_mem_node - > length = * ( u32 * ) p_byte ;
dbg ( " pre-mem length = %8.8x \n " , p_mem_node - > length ) ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( p_mem_node ) ;
return 2 ;
}
p_mem_node - > next = ctrl - > p_mem_head ;
ctrl - > p_mem_head = p_mem_node ;
}
while ( numio - - ) {
2006-12-13 11:35:56 +03:00
io_node = kmalloc ( sizeof ( struct pci_resource ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! io_node )
break ;
io_node - > base = * ( u32 * ) p_byte ;
dbg ( " io base = %8.8x \n " , io_node - > base ) ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( io_node ) ;
return 2 ;
}
io_node - > length = * ( u32 * ) p_byte ;
dbg ( " io length = %8.8x \n " , io_node - > length ) ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( io_node ) ;
return 2 ;
}
io_node - > next = ctrl - > io_head ;
ctrl - > io_head = io_node ;
}
while ( numbus - - ) {
2006-12-13 11:35:56 +03:00
bus_node = kmalloc ( sizeof ( struct pci_resource ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! bus_node )
break ;
bus_node - > base = * ( u32 * ) p_byte ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( bus_node ) ;
return 2 ;
}
bus_node - > length = * ( u32 * ) p_byte ;
p_byte + = 4 ;
if ( p_byte > ( ( u8 * ) p_EV_header + evbuffer_length ) ) {
kfree ( bus_node ) ;
return 2 ;
}
bus_node - > next = ctrl - > bus_head ;
ctrl - > bus_head = bus_node ;
}
2009-03-31 19:23:16 +04:00
/* If all of the following fail, we don't have any resources for
* hot plug add
*/
2005-04-17 02:20:36 +04:00
rc = 1 ;
rc & = cpqhp_resource_sort_and_combine ( & ( ctrl - > mem_head ) ) ;
rc & = cpqhp_resource_sort_and_combine ( & ( ctrl - > p_mem_head ) ) ;
rc & = cpqhp_resource_sort_and_combine ( & ( ctrl - > io_head ) ) ;
rc & = cpqhp_resource_sort_and_combine ( & ( ctrl - > bus_head ) ) ;
if ( rc )
return ( rc ) ;
} else {
2009-03-31 19:23:11 +04:00
if ( ( evbuffer [ 0 ] ! = 0 ) & & ( ! ctrl - > push_flag ) )
2005-04-17 02:20:36 +04:00
return 1 ;
}
return 0 ;
}
2009-03-31 19:23:11 +04:00
2005-04-17 02:20:36 +04:00
int compaq_nvram_store ( void __iomem * rom_start )
{
int rc = 1 ;
if ( rom_start = = NULL )
return - ENODEV ;
if ( evbuffer_init ) {
rc = store_HRT ( rom_start ) ;
if ( rc ) {
err ( msg_unable_to_save ) ;
}
}
return rc ;
}