aic94xx: aic94xx_dev.c Sequencer DDB management
PATCH 4/20 aic94xx: aic94xx_dev.c Sequencer DDB management
Signed-off-by Luben Tuikov <[email protected]>
drivers/scsi/aic94xx/aic94xx_dev.c +350 -0
@@ -0,0 +1,350 @@ +/* +* Aic94xx SAS/SATA DDB management +* +* Copyright (C) 2005 Adaptec, Inc. All rights reserved. +* Copyright (C) 2005 Luben Tuikov <[email protected]> +* +* This file is licensed under GPLv2. +* +* This file is part of the aic94xx driver. +* +* The aic94xx driver 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. +* +* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software +* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +* +* $Id: //depot/aic94xx/aic94xx_dev.c#20 $ +*/ + +#include "aic94xx.h" +#include "aic94xx_hwi.h" +#include "aic94xx_reg.h" +#include "aic94xx_sas.h" + +#define FIND_FREE_DDB (_ha) find_first_zero_bit((_ha)->hw_prof.ddb_bitmap, \ + (_ha)->hw_prof.max_ddbs) +#define SET_DDB (_ddb, _ha) set_bit(_ddb, (_ha)->hw_prof.ddb_bitmap) +#define CLEAR_DDB (_ddb, _ha) clear_bit(_ddb, (_ha)->hw_prof.ddb_bitmap) + +static inline int asd_get_ddb (struct asd_ha_struct *asd_ha) +{ +unsigned long flags; +int ddb, i; + +spin_lock_irqsave (&asd_ha->hw_prof.ddb_lock, flags); + ddb =FIND_FREE_DDB (asd_ha); +if (ddb >= asd_ha->hw_prof.max_ddbs) { + ddb = -ENOMEM ; +spin_unlock_irqrestore (&asd_ha->hw_prof.ddb_lock, flags); + goto out; + } +SET_DDB (ddb, asd_ha); +spin_unlock_irqrestore (&asd_ha->hw_prof.ddb_lock, flags); + +for (i =0 ; i <sizeof (struct asd_ddb_ssp_smp_target_port ); i+=4 ) +asd_ddbsite_write_dword (asd_ha, ddb, i,0 ); +out: +return ddb; +} + +#define INIT_CONN_TAG offsetof(struct asd_ddb_ssp_smp_target_port, init_conn_tag) +#define DEST_SAS_ADDR offsetof(struct asd_ddb_ssp_smp_target_port, dest_sas_addr) +#define SEND_QUEUE_HEAD offsetof(struct asd_ddb_ssp_smp_target_port, send_queue_head) +#define DDB_TYPE offsetof(struct asd_ddb_ssp_smp_target_port, ddb_type) +#define CONN_MASK offsetof(struct asd_ddb_ssp_smp_target_port, conn_mask) +#define DDB_TARG_FLAGS offsetof(struct asd_ddb_ssp_smp_target_port, flags) +#define DDB_TARG_FLAGS2 offsetof(struct asd_ddb_stp_sata_target_port, flags2) +#define EXEC_QUEUE_TAIL offsetof(struct asd_ddb_ssp_smp_target_port, exec_queue_tail) +#define SEND_QUEUE_TAIL offsetof(struct asd_ddb_ssp_smp_target_port, send_queue_tail) +#define SISTER_DDB offsetof(struct asd_ddb_ssp_smp_target_port, sister_ddb) +#define MAX_CCONN offsetof(struct asd_ddb_ssp_smp_target_port, max_concurrent_conn) +#define NUM_CTX offsetof(struct asd_ddb_ssp_smp_target_port, num_contexts) +#define ATA_CMD_SCBPTR offsetof(struct asd_ddb_stp_sata_target_port, ata_cmd_scbptr) +#define SATA_TAG_ALLOC_MASK offsetof(struct asd_ddb_stp_sata_target_port, sata_tag_alloc_mask) +#define NUM_SATA_TAGS offsetof(struct asd_ddb_stp_sata_target_port, num_sata_tags) +#define SATA_STATUS offsetof(struct asd_ddb_stp_sata_target_port, sata_status) +#define NCQ_DATA_SCB_PTR offsetof(struct asd_ddb_stp_sata_target_port, ncq_data_scb_ptr) +#define ITNL_TIMEOUT offsetof(struct asd_ddb_ssp_smp_target_port, itnl_timeout) + +static inline void asd_free_ddb (struct asd_ha_struct *asd_ha,int ddb) +{ +unsigned long flags; + +if (!ddb || ddb >=0xFFFF ) +return ; +asd_ddbsite_write_byte (asd_ha, ddb,DDB_TYPE ,DDB_TYPE_UNUSED ); +spin_lock_irqsave (&asd_ha->hw_prof.ddb_lock, flags); +CLEAR_DDB (ddb, asd_ha); +spin_unlock_irqrestore (&asd_ha->hw_prof.ddb_lock, flags); +} + +static inline void asd_set_ddb_type (struct domain_device *dev) +{ +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; +int ddb = (int ) (unsigned long ) dev->lldd_dev; + +if (dev->dev_type ==SATA_PM_PORT ) +asd_ddbsite_write_byte (asd_ha,ddb,DDB_TYPE ,DDB_TYPE_PM_PORT ); +else if (dev->tproto) +asd_ddbsite_write_byte (asd_ha,ddb,DDB_TYPE ,DDB_TYPE_TARGET ); +else +asd_ddbsite_write_byte (asd_ha,ddb,DDB_TYPE ,DDB_TYPE_INITIATOR ); +} + +static int asd_init_sata_tag_ddb (struct domain_device *dev) +{ +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; +int ddb, i; + + ddb =asd_get_ddb (asd_ha); +if (ddb <0 ) +return ddb; + +for (i =0 ; i <sizeof (struct asd_ddb_sata_tag ); i +=2 ) +asd_ddbsite_write_word (asd_ha, ddb, i,0xFFFF ); + +asd_ddbsite_write_word (asd_ha, (int ) (unsigned long ) dev->lldd_dev, +SISTER_DDB , ddb); +return 0 ; +} + +static inline int asd_init_sata (struct domain_device *dev) +{ +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; +int ddb = (int ) (unsigned long ) dev->lldd_dev; +u32 qdepth =0 ; +int res =0 ; + +asd_ddbsite_write_word (asd_ha, ddb,ATA_CMD_SCBPTR ,0xFFFF ); +if ((dev->dev_type ==SATA_DEV || dev->dev_type ==SATA_PM_PORT ) && + dev->sata_dev.identify_device && + dev->sata_dev.identify_device[10 ] !=0 ) { +u16 w75 =le16_to_cpu (dev->sata_dev.identify_device[75 ]); +u16 w76 =le16_to_cpu (dev->sata_dev.identify_device[76 ]); + +if (w76 &0x100 )/* NCQ? */ + qdepth = (w75 &0x1F ) +1 ; +asd_ddbsite_write_dword (asd_ha, ddb,SATA_TAG_ALLOC_MASK , + (1 <<qdepth)-1 ); +asd_ddbsite_write_byte (asd_ha, ddb,NUM_SATA_TAGS , qdepth); + } +if (dev->dev_type ==SATA_DEV || dev->dev_type ==SATA_PM || + dev->dev_type ==SATA_PM_PORT ) { +struct dev_to_host_fis *fis = (struct dev_to_host_fis *) + dev->frame_rcvd; +asd_ddbsite_write_byte (asd_ha, ddb,SATA_STATUS , fis->status); + } +asd_ddbsite_write_word (asd_ha, ddb,NCQ_DATA_SCB_PTR ,0xFFFF ); +if (qdepth >0 ) + res =asd_init_sata_tag_ddb (dev); +return res; +} + +static int asd_init_target_ddb (struct domain_device *dev) +{ +int ddb, i; +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; +u8 flags =0 ; + + ddb =asd_get_ddb (asd_ha); +if (ddb <0 ) +return ddb; + + dev->lldd_dev = (void *) (unsigned long ) ddb; + +asd_ddbsite_write_byte (asd_ha, ddb,0 ,DDB_TP_CONN_TYPE ); +asd_ddbsite_write_byte (asd_ha, ddb,1 ,0 ); +asd_ddbsite_write_word (asd_ha, ddb,INIT_CONN_TAG ,0xFFFF ); +for (i =0 ; i <SAS_ADDR_SIZE ; i++) +asd_ddbsite_write_byte (asd_ha, ddb,DEST_SAS_ADDR +i, + dev->sas_addr[i]); +asd_ddbsite_write_word (asd_ha, ddb,SEND_QUEUE_HEAD ,0xFFFF ); +asd_set_ddb_type (dev); +asd_ddbsite_write_byte (asd_ha, ddb,CONN_MASK , dev->port->phy_mask); +if (dev->port->oob_mode !=SATA_OOB_MODE ) { + flags |=OPEN_REQUIRED ; +if ((dev->dev_type ==SATA_DEV ) || + (dev->tproto &SAS_PROTO_STP )) { +struct smp_resp *rps_resp = &dev->sata_dev.rps_resp; +if (rps_resp->frame_type ==SMP_RESPONSE && + rps_resp->function ==SMP_REPORT_PHY_SATA && + rps_resp->result ==SMP_RESP_FUNC_ACC ) { +if (rps_resp->rps.affil_valid) + flags |=STP_AFFIL_POL ; +if (rps_resp->rps.affil_supp) + flags |=SUPPORTS_AFFIL ; + } + }else { + flags |=CONCURRENT_CONN_SUPP ; +if (!dev->parent && + (dev->dev_type ==EDGE_DEV || + dev->dev_type ==FANOUT_DEV )) +asd_ddbsite_write_byte (asd_ha, ddb,MAX_CCONN , +4 ); +else +asd_ddbsite_write_byte (asd_ha, ddb,MAX_CCONN , + dev->pathways); +asd_ddbsite_write_byte (asd_ha, ddb,NUM_CTX ,1 ); + } + } +if (dev->dev_type ==SATA_PM ) + flags |=SATA_MULTIPORT ; +asd_ddbsite_write_byte (asd_ha, ddb,DDB_TARG_FLAGS , flags); + + flags =0 ; +if (dev->tproto &SAS_PROTO_STP ) + flags |=STP_CL_POL_NO_TX ; +asd_ddbsite_write_byte (asd_ha, ddb,DDB_TARG_FLAGS2 , flags); + +asd_ddbsite_write_word (asd_ha, ddb,EXEC_QUEUE_TAIL ,0xFFFF ); +asd_ddbsite_write_word (asd_ha, ddb,SEND_QUEUE_TAIL ,0xFFFF ); +asd_ddbsite_write_word (asd_ha, ddb,SISTER_DDB ,0xFFFF ); + +if (dev->dev_type ==SATA_DEV || (dev->tproto &SAS_PROTO_STP )) { + i =asd_init_sata (dev); +if (i <0 ) { +asd_free_ddb (asd_ha, ddb); +return i; + } + } + +if (dev->dev_type ==SAS_END_DEV && dev->end_dev.itnl_timeout >0 ) +asd_ddbsite_write_word (asd_ha, ddb,ITNL_TIMEOUT , +min (dev->end_dev.itnl_timeout, + (u16 )ITNL_TIMEOUT_CONST )); +else +asd_ddbsite_write_word (asd_ha, ddb,ITNL_TIMEOUT , + (u16 )ITNL_TIMEOUT_CONST ); +return 0 ; +} + +static int asd_init_sata_pm_table_ddb (struct domain_device *dev) +{ +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; +int ddb, i; + + ddb =asd_get_ddb (asd_ha); +if (ddb <0 ) +return ddb; + +for (i =0 ; i <32 ; i +=2 ) +asd_ddbsite_write_word (asd_ha, ddb, i,0xFFFF ); + +asd_ddbsite_write_word (asd_ha, (int ) (unsigned long ) dev->lldd_dev, +SISTER_DDB , ddb); + +return 0 ; +} + +#define PM_PORT_FLAGS offsetof(struct asd_ddb_sata_pm_port, pm_port_flags) +#define PARENT_DDB offsetof(struct asd_ddb_sata_pm_port, parent_ddb) + +/** +* asd_init_sata_pm_port_ddb -- SATA Port Multiplier Port +* dev: pointer to domain device +* +* For SATA Port Multiplier Ports we need to allocate one SATA Port +* Multiplier Port DDB and depending on whether the target on it +* supports SATA II NCQ, one SATA Tag DDB. +*/ +static int asd_init_sata_pm_port_ddb (struct domain_device *dev) +{ +int ddb, i, parent_ddb, pmtable_ddb; +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; +u8 flags; + + ddb =asd_get_ddb (asd_ha); +if (ddb <0 ) +return ddb; + +asd_set_ddb_type (dev); + flags = (dev->sata_dev.port_no <<4 ) |PM_PORT_SET ; +asd_ddbsite_write_byte (asd_ha, ddb,PM_PORT_FLAGS , flags); +asd_ddbsite_write_word (asd_ha, ddb,SISTER_DDB ,0xFFFF ); +asd_ddbsite_write_word (asd_ha, ddb,ATA_CMD_SCBPTR ,0xFFFF ); +asd_init_sata (dev); + + parent_ddb = (int ) (unsigned long ) dev->parent->lldd_dev; +asd_ddbsite_write_word (asd_ha, ddb,PARENT_DDB , parent_ddb); + pmtable_ddb =asd_ddbsite_read_word (asd_ha, parent_ddb,SISTER_DDB ); +asd_ddbsite_write_word (asd_ha, pmtable_ddb, dev->sata_dev.port_no,ddb); + +if (asd_ddbsite_read_byte (asd_ha, ddb,NUM_SATA_TAGS ) >0 ) { + i =asd_init_sata_tag_ddb (dev); +if (i <0 ) { +asd_free_ddb (asd_ha, ddb); +return i; + } + } +return 0 ; +} + +static int asd_init_initiator_ddb (struct domain_device *dev) +{ +return -ENODEV ; +} + +/** +* asd_init_sata_pm_ddb -- SATA Port Multiplier +* dev: pointer to domain device +* +* For STP and direct-attached SATA Port Multipliers we need +* one target port DDB entry and one SATA PM table DDB entry. +*/ +static int asd_init_sata_pm_ddb (struct domain_device *dev) +{ +int res =0 ; + + res =asd_init_target_ddb (dev); +if (res) + goto out; + res =asd_init_sata_pm_table_ddb (dev); +if (res) +asd_free_ddb (dev->port->ha->lldd_ha, + (int ) (unsigned long ) dev->lldd_dev); +out: +return res; +} + +int asd_dev_found (struct domain_device *dev) +{ +int res =0 ; + +switch (dev->dev_type) { +case SATA_PM : + res =asd_init_sata_pm_ddb (dev); +break ; +case SATA_PM_PORT : + res =asd_init_sata_pm_port_ddb (dev); +break ; +default : +if (dev->tproto) + res =asd_init_target_ddb (dev); +else + res =asd_init_initiator_ddb (dev); + } +return res; +} + +void asd_dev_gone (struct domain_device *dev) +{ +int ddb, sister_ddb; +struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; + + ddb = (int ) (unsigned long ) dev->lldd_dev; + sister_ddb =asd_ddbsite_read_word (asd_ha, ddb,SISTER_DDB ); + +if (sister_ddb !=0xFFFF ) +asd_free_ddb (asd_ha, sister_ddb); +asd_free_ddb (asd_ha, ddb); + dev->lldd_dev =NULL ; +}