aic94xx_dev.c 11.0 KB
Newer Older
J
James Bottomley 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
/*
 * Aic94xx SAS/SATA DDB management
 *
 * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
 * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
 *
 * 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#21 $
 */

#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) {
		struct sas_end_device *rdev = rphy_to_end_device(dev->rphy);
		if (rdev->I_T_nexus_loss_timeout > 0)
			asd_ddbsite_write_word(asd_ha, ddb, ITNL_TIMEOUT,
					       min(rdev->I_T_nexus_loss_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;
}