2005-04-17 02:20:36 +04:00
/*
* Copyright ( C ) 2004 IBM Corporation
*
* Authors :
* Leendert van Doorn < leendert @ watson . ibm . com >
* Dave Safford < safford @ watson . ibm . com >
* Reiner Sailer < sailer @ watson . ibm . com >
* Kylene Hall < kjhall @ us . ibm . com >
*
* Maintained by : < tpmdd_devel @ lists . sourceforge . net >
*
* Device driver for TCG / TCPA TPM ( trusted platform module ) .
* Specifications at www . trustedcomputinggroup . org
*
* 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 , version 2 of the
* License .
*
*/
2005-11-01 10:44:28 +03:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include "tpm.h"
/* National definitions */
2005-06-24 09:02:06 +04:00
enum tpm_nsc_addr {
TPM_NSC_IRQ = 0x07 ,
TPM_NSC_BASE0_HI = 0x60 ,
TPM_NSC_BASE0_LO = 0x61 ,
TPM_NSC_BASE1_HI = 0x62 ,
TPM_NSC_BASE1_LO = 0x63
2005-06-24 09:01:48 +04:00
} ;
2005-04-17 02:20:36 +04:00
2005-06-24 09:01:48 +04:00
enum tpm_nsc_index {
NSC_LDN_INDEX = 0x07 ,
NSC_SID_INDEX = 0x20 ,
NSC_LDC_INDEX = 0x30 ,
NSC_DIO_INDEX = 0x60 ,
NSC_CIO_INDEX = 0x62 ,
NSC_IRQ_INDEX = 0x70 ,
NSC_ITS_INDEX = 0x71
} ;
2005-04-17 02:20:36 +04:00
2005-06-24 09:01:48 +04:00
enum tpm_nsc_status_loc {
NSC_STATUS = 0x01 ,
NSC_COMMAND = 0x01 ,
NSC_DATA = 0x00
} ;
2005-04-17 02:20:36 +04:00
/* status bits */
2005-06-24 09:02:06 +04:00
enum tpm_nsc_status {
2005-06-24 09:01:48 +04:00
NSC_STATUS_OBF = 0x01 , /* output buffer full */
NSC_STATUS_IBF = 0x02 , /* input buffer full */
NSC_STATUS_F0 = 0x04 , /* F0 */
NSC_STATUS_A2 = 0x08 , /* A2 */
NSC_STATUS_RDY = 0x10 , /* ready to receive command */
NSC_STATUS_IBR = 0x20 /* ready to receive data */
} ;
2005-06-26 01:55:39 +04:00
2005-04-17 02:20:36 +04:00
/* command bits */
2005-06-24 09:01:48 +04:00
enum tpm_nsc_cmd_mode {
NSC_COMMAND_NORMAL = 0x01 , /* normal mode */
NSC_COMMAND_EOC = 0x03 ,
NSC_COMMAND_CANCEL = 0x22
} ;
2005-04-17 02:20:36 +04:00
/*
* Wait for a certain status to appear
*/
static int wait_for_stat ( struct tpm_chip * chip , u8 mask , u8 val , u8 * data )
{
2005-06-24 09:01:47 +04:00
unsigned long stop ;
2005-04-17 02:20:36 +04:00
/* status immediately available check */
2006-04-22 13:37:15 +04:00
* data = inb ( chip - > vendor . base + NSC_STATUS ) ;
2005-04-17 02:20:36 +04:00
if ( ( * data & mask ) = = val )
return 0 ;
/* wait for status */
2005-06-24 09:01:47 +04:00
stop = jiffies + 10 * HZ ;
2005-04-17 02:20:36 +04:00
do {
2005-06-24 09:01:47 +04:00
msleep ( TPM_TIMEOUT ) ;
2006-04-22 13:37:15 +04:00
* data = inb ( chip - > vendor . base + 1 ) ;
2005-06-24 09:01:47 +04:00
if ( ( * data & mask ) = = val )
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-06-24 09:01:47 +04:00
while ( time_before ( jiffies , stop ) ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
static int nsc_wait_for_ready ( struct tpm_chip * chip )
{
int status ;
2005-06-24 09:01:47 +04:00
unsigned long stop ;
2005-04-17 02:20:36 +04:00
/* status immediately available check */
2006-04-22 13:37:15 +04:00
status = inb ( chip - > vendor . base + NSC_STATUS ) ;
2005-04-17 02:20:36 +04:00
if ( status & NSC_STATUS_OBF )
2006-04-22 13:37:15 +04:00
status = inb ( chip - > vendor . base + NSC_DATA ) ;
2005-04-17 02:20:36 +04:00
if ( status & NSC_STATUS_RDY )
return 0 ;
/* wait for status */
2005-06-24 09:01:47 +04:00
stop = jiffies + 100 ;
2005-04-17 02:20:36 +04:00
do {
2005-06-24 09:01:47 +04:00
msleep ( TPM_TIMEOUT ) ;
2006-04-22 13:37:15 +04:00
status = inb ( chip - > vendor . base + NSC_STATUS ) ;
2005-04-17 02:20:36 +04:00
if ( status & NSC_STATUS_OBF )
2006-04-22 13:37:15 +04:00
status = inb ( chip - > vendor . base + NSC_DATA ) ;
2005-06-24 09:01:47 +04:00
if ( status & NSC_STATUS_RDY )
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-06-24 09:01:47 +04:00
while ( time_before ( jiffies , stop ) ) ;
2005-04-17 02:20:36 +04:00
2005-10-31 02:03:24 +03:00
dev_info ( chip - > dev , " wait for ready failed \n " ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
static int tpm_nsc_recv ( struct tpm_chip * chip , u8 * buf , size_t count )
{
u8 * buffer = buf ;
u8 data , * p ;
u32 size ;
__be32 * native_size ;
if ( count < 6 )
return - EIO ;
if ( wait_for_stat ( chip , NSC_STATUS_F0 , NSC_STATUS_F0 , & data ) < 0 ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev , " F0 timeout \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
if ( ( data =
2006-04-22 13:37:15 +04:00
inb ( chip - > vendor . base + NSC_DATA ) ) ! = NSC_COMMAND_NORMAL ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev , " not in normal mode (0x%x) \n " ,
2005-04-17 02:20:36 +04:00
data ) ;
return - EIO ;
}
/* read the whole packet */
for ( p = buffer ; p < & buffer [ count ] ; p + + ) {
if ( wait_for_stat
( chip , NSC_STATUS_OBF , NSC_STATUS_OBF , & data ) < 0 ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev ,
2005-04-17 02:20:36 +04:00
" OBF timeout (while reading data) \n " ) ;
return - EIO ;
}
if ( data & NSC_STATUS_F0 )
break ;
2006-04-22 13:37:15 +04:00
* p = inb ( chip - > vendor . base + NSC_DATA ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:55:39 +04:00
if ( ( data & NSC_STATUS_F0 ) = = 0 & &
( wait_for_stat ( chip , NSC_STATUS_F0 , NSC_STATUS_F0 , & data ) < 0 ) ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev , " F0 not set \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
2006-04-22 13:37:15 +04:00
if ( ( data = inb ( chip - > vendor . base + NSC_DATA ) ) ! = NSC_COMMAND_EOC ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev ,
2005-04-17 02:20:36 +04:00
" expected end of command(0x%x) \n " , data ) ;
return - EIO ;
}
native_size = ( __force __be32 * ) ( buf + 2 ) ;
size = be32_to_cpu ( * native_size ) ;
if ( count < size )
return - EIO ;
return size ;
}
static int tpm_nsc_send ( struct tpm_chip * chip , u8 * buf , size_t count )
{
u8 data ;
int i ;
/*
* If we hit the chip with back to back commands it locks up
* and never set IBF . Hitting it with this " hammer " seems to
* fix it . Not sure why this is needed , we followed the flow
* chart in the manual to the letter .
*/
2006-04-22 13:37:15 +04:00
outb ( NSC_COMMAND_CANCEL , chip - > vendor . base + NSC_COMMAND ) ;
2005-04-17 02:20:36 +04:00
if ( nsc_wait_for_ready ( chip ) ! = 0 )
return - EIO ;
if ( wait_for_stat ( chip , NSC_STATUS_IBF , 0 , & data ) < 0 ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev , " IBF timeout \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
2006-04-22 13:37:15 +04:00
outb ( NSC_COMMAND_NORMAL , chip - > vendor . base + NSC_COMMAND ) ;
2005-04-17 02:20:36 +04:00
if ( wait_for_stat ( chip , NSC_STATUS_IBR , NSC_STATUS_IBR , & data ) < 0 ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev , " IBR timeout \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
for ( i = 0 ; i < count ; i + + ) {
if ( wait_for_stat ( chip , NSC_STATUS_IBF , 0 , & data ) < 0 ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev ,
2005-04-17 02:20:36 +04:00
" IBF timeout (while writing data) \n " ) ;
return - EIO ;
}
2006-04-22 13:37:15 +04:00
outb ( buf [ i ] , chip - > vendor . base + NSC_DATA ) ;
2005-04-17 02:20:36 +04:00
}
if ( wait_for_stat ( chip , NSC_STATUS_IBF , 0 , & data ) < 0 ) {
2005-10-31 02:03:24 +03:00
dev_err ( chip - > dev , " IBF timeout \n " ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
}
2006-04-22 13:37:15 +04:00
outb ( NSC_COMMAND_EOC , chip - > vendor . base + NSC_COMMAND ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
static void tpm_nsc_cancel ( struct tpm_chip * chip )
{
2006-04-22 13:37:15 +04:00
outb ( NSC_COMMAND_CANCEL , chip - > vendor . base + NSC_COMMAND ) ;
2005-04-17 02:20:36 +04:00
}
2005-10-31 02:03:23 +03:00
static u8 tpm_nsc_status ( struct tpm_chip * chip )
{
2006-04-22 13:37:15 +04:00
return inb ( chip - > vendor . base + NSC_STATUS ) ;
2005-10-31 02:03:23 +03:00
}
2006-07-03 11:24:21 +04:00
static const struct file_operations nsc_ops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. open = tpm_open ,
. read = tpm_read ,
. write = tpm_write ,
. release = tpm_release ,
} ;
2005-06-24 09:02:00 +04:00
static DEVICE_ATTR ( pubek , S_IRUGO , tpm_show_pubek , NULL ) ;
static DEVICE_ATTR ( pcrs , S_IRUGO , tpm_show_pcrs , NULL ) ;
static DEVICE_ATTR ( caps , S_IRUGO , tpm_show_caps , NULL ) ;
static DEVICE_ATTR ( cancel , S_IWUSR | S_IWGRP , NULL , tpm_store_cancel ) ;
static struct attribute * nsc_attrs [ ] = {
& dev_attr_pubek . attr ,
& dev_attr_pcrs . attr ,
& dev_attr_caps . attr ,
& dev_attr_cancel . attr ,
2005-10-31 02:03:29 +03:00
NULL ,
2005-06-24 09:02:00 +04:00
} ;
static struct attribute_group nsc_attr_grp = { . attrs = nsc_attrs } ;
2006-04-22 13:37:26 +04:00
static const struct tpm_vendor_specific tpm_nsc = {
2005-04-17 02:20:36 +04:00
. recv = tpm_nsc_recv ,
. send = tpm_nsc_send ,
. cancel = tpm_nsc_cancel ,
2005-10-31 02:03:23 +03:00
. status = tpm_nsc_status ,
2005-04-17 02:20:36 +04:00
. req_complete_mask = NSC_STATUS_OBF ,
. req_complete_val = NSC_STATUS_OBF ,
2005-06-24 09:02:02 +04:00
. req_canceled = NSC_STATUS_RDY ,
2005-06-24 09:02:00 +04:00
. attr_group = & nsc_attr_grp ,
2005-04-17 02:20:36 +04:00
. miscdev = { . fops = & nsc_ops , } ,
} ;
2005-10-31 02:03:26 +03:00
static struct platform_device * pdev = NULL ;
static void __devexit tpm_nsc_remove ( struct device * dev )
{
struct tpm_chip * chip = dev_get_drvdata ( dev ) ;
if ( chip ) {
2006-04-22 13:37:15 +04:00
release_region ( chip - > vendor . base , 2 ) ;
2005-10-31 02:03:26 +03:00
tpm_remove_hardware ( chip - > dev ) ;
}
}
static struct device_driver nsc_drv = {
. name = " tpm_nsc " ,
. bus = & platform_bus_type ,
. owner = THIS_MODULE ,
. suspend = tpm_pm_suspend ,
. resume = tpm_pm_resume ,
} ;
static int __init init_nsc ( void )
2005-04-17 02:20:36 +04:00
{
int rc = 0 ;
2005-06-24 09:02:06 +04:00
int lo , hi ;
2005-06-26 01:55:39 +04:00
int nscAddrBase = TPM_ADDR ;
2006-04-22 13:37:26 +04:00
struct tpm_chip * chip ;
unsigned long base ;
2005-06-26 01:55:39 +04:00
2005-04-17 02:20:36 +04:00
/* verify that it is a National part (SID) */
2005-06-26 01:55:39 +04:00
if ( tpm_read_index ( TPM_ADDR , NSC_SID_INDEX ) ! = 0xEF ) {
nscAddrBase = ( tpm_read_index ( TPM_SUPERIO_ADDR , 0x2C ) < < 8 ) |
( tpm_read_index ( TPM_SUPERIO_ADDR , 0x2B ) & 0xFE ) ;
2005-10-31 02:03:26 +03:00
if ( tpm_read_index ( nscAddrBase , NSC_SID_INDEX ) ! = 0xF6 )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
2005-11-07 11:59:25 +03:00
driver_register ( & nsc_drv ) ;
2005-06-26 01:55:39 +04:00
hi = tpm_read_index ( nscAddrBase , TPM_NSC_BASE0_HI ) ;
lo = tpm_read_index ( nscAddrBase , TPM_NSC_BASE0_LO ) ;
2006-04-22 13:37:26 +04:00
base = ( hi < < 8 ) | lo ;
2005-06-26 01:55:39 +04:00
2005-10-31 02:03:26 +03:00
/* enable the DPM module */
tpm_write_index ( nscAddrBase , NSC_LDC_INDEX , 0x01 ) ;
2005-11-07 11:59:25 +03:00
pdev = kzalloc ( sizeof ( struct platform_device ) , GFP_KERNEL ) ;
if ( ! pdev ) {
rc = - ENOMEM ;
goto err_unreg_drv ;
}
2005-10-31 02:03:26 +03:00
pdev - > name = " tpm_nscl0 " ;
pdev - > id = - 1 ;
pdev - > num_resources = 0 ;
pdev - > dev . release = tpm_nsc_remove ;
pdev - > dev . driver = & nsc_drv ;
2005-11-07 11:59:25 +03:00
if ( ( rc = platform_device_register ( pdev ) ) < 0 )
goto err_free_dev ;
2005-10-31 02:03:26 +03:00
2006-04-22 13:37:26 +04:00
if ( request_region ( base , 2 , " tpm_nsc0 " ) = = NULL ) {
2005-11-07 11:59:25 +03:00
rc = - EBUSY ;
goto err_unreg_dev ;
2005-10-31 02:03:26 +03:00
}
2006-04-22 13:37:26 +04:00
if ( ! ( chip = tpm_register_hardware ( & pdev - > dev , & tpm_nsc ) ) ) {
rc = - ENODEV ;
2005-11-07 11:59:25 +03:00
goto err_rel_reg ;
2006-04-22 13:37:26 +04:00
}
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev , " NSC TPM detected \n " ) ;
dev_dbg ( & pdev - > dev ,
2005-04-17 02:20:36 +04:00
" NSC LDN 0x%x, SID 0x%x, SRID 0x%x \n " ,
2005-06-26 01:55:39 +04:00
tpm_read_index ( nscAddrBase , 0x07 ) , tpm_read_index ( nscAddrBase , 0x20 ) ,
tpm_read_index ( nscAddrBase , 0x27 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev ,
2005-04-17 02:20:36 +04:00
" NSC SIOCF1 0x%x SIOCF5 0x%x SIOCF6 0x%x SIOCF8 0x%x \n " ,
2005-06-26 01:55:39 +04:00
tpm_read_index ( nscAddrBase , 0x21 ) , tpm_read_index ( nscAddrBase , 0x25 ) ,
tpm_read_index ( nscAddrBase , 0x26 ) , tpm_read_index ( nscAddrBase , 0x28 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev , " NSC IO Base0 0x%x \n " ,
2005-06-26 01:55:39 +04:00
( tpm_read_index ( nscAddrBase , 0x60 ) < < 8 ) | tpm_read_index ( nscAddrBase , 0x61 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev , " NSC IO Base1 0x%x \n " ,
2005-06-26 01:55:39 +04:00
( tpm_read_index ( nscAddrBase , 0x62 ) < < 8 ) | tpm_read_index ( nscAddrBase , 0x63 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev , " NSC Interrupt number and wakeup 0x%x \n " ,
2005-06-26 01:55:39 +04:00
tpm_read_index ( nscAddrBase , 0x70 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev , " NSC IRQ type select 0x%x \n " ,
2005-06-26 01:55:39 +04:00
tpm_read_index ( nscAddrBase , 0x71 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev ,
2005-04-17 02:20:36 +04:00
" NSC DMA channel select0 0x%x, select1 0x%x \n " ,
2005-06-26 01:55:39 +04:00
tpm_read_index ( nscAddrBase , 0x74 ) , tpm_read_index ( nscAddrBase , 0x75 ) ) ;
2005-10-31 02:03:26 +03:00
dev_dbg ( & pdev - > dev ,
2005-04-17 02:20:36 +04:00
" NSC Config "
" 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x \n " ,
2005-06-26 01:55:39 +04:00
tpm_read_index ( nscAddrBase , 0xF0 ) , tpm_read_index ( nscAddrBase , 0xF1 ) ,
tpm_read_index ( nscAddrBase , 0xF2 ) , tpm_read_index ( nscAddrBase , 0xF3 ) ,
tpm_read_index ( nscAddrBase , 0xF4 ) , tpm_read_index ( nscAddrBase , 0xF5 ) ,
tpm_read_index ( nscAddrBase , 0xF6 ) , tpm_read_index ( nscAddrBase , 0xF7 ) ,
tpm_read_index ( nscAddrBase , 0xF8 ) , tpm_read_index ( nscAddrBase , 0xF9 ) ) ;
2005-04-17 02:20:36 +04:00
2005-10-31 02:03:26 +03:00
dev_info ( & pdev - > dev ,
2005-06-26 01:55:39 +04:00
" NSC TPM revision %d \n " ,
tpm_read_index ( nscAddrBase , 0x27 ) & 0x1F ) ;
2005-04-17 02:20:36 +04:00
2006-04-22 13:37:26 +04:00
chip - > vendor . base = base ;
2005-04-17 02:20:36 +04:00
return 0 ;
2005-11-07 11:59:25 +03:00
err_rel_reg :
2006-04-22 13:37:26 +04:00
release_region ( base , 2 ) ;
2005-11-07 11:59:25 +03:00
err_unreg_dev :
platform_device_unregister ( pdev ) ;
err_free_dev :
kfree ( pdev ) ;
err_unreg_drv :
driver_unregister ( & nsc_drv ) ;
return rc ;
2005-04-17 02:20:36 +04:00
}
static void __exit cleanup_nsc ( void )
{
2005-10-31 02:03:26 +03:00
if ( pdev ) {
tpm_nsc_remove ( & pdev - > dev ) ;
platform_device_unregister ( pdev ) ;
kfree ( pdev ) ;
pdev = NULL ;
}
driver_unregister ( & nsc_drv ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( init_nsc ) ;
module_exit ( cleanup_nsc ) ;
MODULE_AUTHOR ( " Leendert van Doorn (leendert@watson.ibm.com) " ) ;
MODULE_DESCRIPTION ( " TPM Driver " ) ;
MODULE_VERSION ( " 2.0 " ) ;
MODULE_LICENSE ( " GPL " ) ;