sas_expander.c

来自「linux 内核源代码」· C语言 代码 · 共 1,932 行 · 第 1/4 页

C
1,932
字号
/* * Serial Attached SCSI (SAS) Expander discovery and configuration * * Copyright (C) 2005 Adaptec, Inc.  All rights reserved. * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com> * * This file is licensed under GPLv2. * * 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.  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA * */#include <linux/scatterlist.h>#include <linux/blkdev.h>#include "sas_internal.h"#include <scsi/scsi_transport.h>#include <scsi/scsi_transport_sas.h>#include "../scsi_sas_internal.h"static int sas_discover_expander(struct domain_device *dev);static int sas_configure_routing(struct domain_device *dev, u8 *sas_addr);static int sas_configure_phy(struct domain_device *dev, int phy_id,			     u8 *sas_addr, int include);static int sas_disable_routing(struct domain_device *dev,  u8 *sas_addr);/* ---------- SMP task management ---------- */static void smp_task_timedout(unsigned long _task){	struct sas_task *task = (void *) _task;	unsigned long flags;	spin_lock_irqsave(&task->task_state_lock, flags);	if (!(task->task_state_flags & SAS_TASK_STATE_DONE))		task->task_state_flags |= SAS_TASK_STATE_ABORTED;	spin_unlock_irqrestore(&task->task_state_lock, flags);	complete(&task->completion);}static void smp_task_done(struct sas_task *task){	if (!del_timer(&task->timer))		return;	complete(&task->completion);}/* Give it some long enough timeout. In seconds. */#define SMP_TIMEOUT 10static int smp_execute_task(struct domain_device *dev, void *req, int req_size,			    void *resp, int resp_size){	int res, retry;	struct sas_task *task = NULL;	struct sas_internal *i =		to_sas_internal(dev->port->ha->core.shost->transportt);	for (retry = 0; retry < 3; retry++) {		task = sas_alloc_task(GFP_KERNEL);		if (!task)			return -ENOMEM;		task->dev = dev;		task->task_proto = dev->tproto;		sg_init_one(&task->smp_task.smp_req, req, req_size);		sg_init_one(&task->smp_task.smp_resp, resp, resp_size);		task->task_done = smp_task_done;		task->timer.data = (unsigned long) task;		task->timer.function = smp_task_timedout;		task->timer.expires = jiffies + SMP_TIMEOUT*HZ;		add_timer(&task->timer);		res = i->dft->lldd_execute_task(task, 1, GFP_KERNEL);		if (res) {			del_timer(&task->timer);			SAS_DPRINTK("executing SMP task failed:%d\n", res);			goto ex_err;		}		wait_for_completion(&task->completion);		res = -ETASK;		if ((task->task_state_flags & SAS_TASK_STATE_ABORTED)) {			SAS_DPRINTK("smp task timed out or aborted\n");			i->dft->lldd_abort_task(task);			if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {				SAS_DPRINTK("SMP task aborted and not done\n");				goto ex_err;			}		}		if (task->task_status.resp == SAS_TASK_COMPLETE &&		    task->task_status.stat == SAM_GOOD) {			res = 0;			break;		} else {			SAS_DPRINTK("%s: task to dev %016llx response: 0x%x "				    "status 0x%x\n", __FUNCTION__,				    SAS_ADDR(dev->sas_addr),				    task->task_status.resp,				    task->task_status.stat);			sas_free_task(task);			task = NULL;		}	}ex_err:	BUG_ON(retry == 3 && task != NULL);	if (task != NULL) {		sas_free_task(task);	}	return res;}/* ---------- Allocations ---------- */static inline void *alloc_smp_req(int size){	u8 *p = kzalloc(size, GFP_KERNEL);	if (p)		p[0] = SMP_REQUEST;	return p;}static inline void *alloc_smp_resp(int size){	return kzalloc(size, GFP_KERNEL);}/* ---------- Expander configuration ---------- */static void sas_set_ex_phy(struct domain_device *dev, int phy_id,			   void *disc_resp){	struct expander_device *ex = &dev->ex_dev;	struct ex_phy *phy = &ex->ex_phy[phy_id];	struct smp_resp *resp = disc_resp;	struct discover_resp *dr = &resp->disc;	struct sas_rphy *rphy = dev->rphy;	int rediscover = (phy->phy != NULL);	if (!rediscover) {		phy->phy = sas_phy_alloc(&rphy->dev, phy_id);		/* FIXME: error_handling */		BUG_ON(!phy->phy);	}	switch (resp->result) {	case SMP_RESP_PHY_VACANT:		phy->phy_state = PHY_VACANT;		return;	default:		phy->phy_state = PHY_NOT_PRESENT;		return;	case SMP_RESP_FUNC_ACC:		phy->phy_state = PHY_EMPTY; /* do not know yet */		break;	}	phy->phy_id = phy_id;	phy->attached_dev_type = dr->attached_dev_type;	phy->linkrate = dr->linkrate;	phy->attached_sata_host = dr->attached_sata_host;	phy->attached_sata_dev  = dr->attached_sata_dev;	phy->attached_sata_ps   = dr->attached_sata_ps;	phy->attached_iproto = dr->iproto << 1;	phy->attached_tproto = dr->tproto << 1;	memcpy(phy->attached_sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE);	phy->attached_phy_id = dr->attached_phy_id;	phy->phy_change_count = dr->change_count;	phy->routing_attr = dr->routing_attr;	phy->virtual = dr->virtual;	phy->last_da_index = -1;	phy->phy->identify.initiator_port_protocols = phy->attached_iproto;	phy->phy->identify.target_port_protocols = phy->attached_tproto;	phy->phy->identify.phy_identifier = phy_id;	phy->phy->minimum_linkrate_hw = dr->hmin_linkrate;	phy->phy->maximum_linkrate_hw = dr->hmax_linkrate;	phy->phy->minimum_linkrate = dr->pmin_linkrate;	phy->phy->maximum_linkrate = dr->pmax_linkrate;	phy->phy->negotiated_linkrate = phy->linkrate;	if (!rediscover)		sas_phy_add(phy->phy);	SAS_DPRINTK("ex %016llx phy%02d:%c attached: %016llx\n",		    SAS_ADDR(dev->sas_addr), phy->phy_id,		    phy->routing_attr == TABLE_ROUTING ? 'T' :		    phy->routing_attr == DIRECT_ROUTING ? 'D' :		    phy->routing_attr == SUBTRACTIVE_ROUTING ? 'S' : '?',		    SAS_ADDR(phy->attached_sas_addr));	return;}#define DISCOVER_REQ_SIZE  16#define DISCOVER_RESP_SIZE 56static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req,				      u8 *disc_resp, int single){	int i, res;	disc_req[9] = single;	for (i = 1 ; i < 3; i++) {		struct discover_resp *dr;		res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE,				       disc_resp, DISCOVER_RESP_SIZE);		if (res)			return res;		/* This is detecting a failure to transmit inital		 * dev to host FIS as described in section G.5 of		 * sas-2 r 04b */		dr = &((struct smp_resp *)disc_resp)->disc;		if (!(dr->attached_dev_type == 0 &&		      dr->attached_sata_dev))			break;		/* In order to generate the dev to host FIS, we		 * send a link reset to the expander port */		sas_smp_phy_control(dev, single, PHY_FUNC_LINK_RESET, NULL);		/* Wait for the reset to trigger the negotiation */		msleep(500);	}	sas_set_ex_phy(dev, single, disc_resp);	return 0;}static int sas_ex_phy_discover(struct domain_device *dev, int single){	struct expander_device *ex = &dev->ex_dev;	int  res = 0;	u8   *disc_req;	u8   *disc_resp;	disc_req = alloc_smp_req(DISCOVER_REQ_SIZE);	if (!disc_req)		return -ENOMEM;	disc_resp = alloc_smp_req(DISCOVER_RESP_SIZE);	if (!disc_resp) {		kfree(disc_req);		return -ENOMEM;	}	disc_req[1] = SMP_DISCOVER;	if (0 <= single && single < ex->num_phys) {		res = sas_ex_phy_discover_helper(dev, disc_req, disc_resp, single);	} else {		int i;		for (i = 0; i < ex->num_phys; i++) {			res = sas_ex_phy_discover_helper(dev, disc_req,							 disc_resp, i);			if (res)				goto out_err;		}	}out_err:	kfree(disc_resp);	kfree(disc_req);	return res;}static int sas_expander_discover(struct domain_device *dev){	struct expander_device *ex = &dev->ex_dev;	int res = -ENOMEM;	ex->ex_phy = kzalloc(sizeof(*ex->ex_phy)*ex->num_phys, GFP_KERNEL);	if (!ex->ex_phy)		return -ENOMEM;	res = sas_ex_phy_discover(dev, -1);	if (res)		goto out_err;	return 0; out_err:	kfree(ex->ex_phy);	ex->ex_phy = NULL;	return res;}#define MAX_EXPANDER_PHYS 128static void ex_assign_report_general(struct domain_device *dev,					    struct smp_resp *resp){	struct report_general_resp *rg = &resp->rg;	dev->ex_dev.ex_change_count = be16_to_cpu(rg->change_count);	dev->ex_dev.max_route_indexes = be16_to_cpu(rg->route_indexes);	dev->ex_dev.num_phys = min(rg->num_phys, (u8)MAX_EXPANDER_PHYS);	dev->ex_dev.conf_route_table = rg->conf_route_table;	dev->ex_dev.configuring = rg->configuring;	memcpy(dev->ex_dev.enclosure_logical_id, rg->enclosure_logical_id, 8);}#define RG_REQ_SIZE   8#define RG_RESP_SIZE 32static int sas_ex_general(struct domain_device *dev){	u8 *rg_req;	struct smp_resp *rg_resp;	int res;	int i;	rg_req = alloc_smp_req(RG_REQ_SIZE);	if (!rg_req)		return -ENOMEM;	rg_resp = alloc_smp_resp(RG_RESP_SIZE);	if (!rg_resp) {		kfree(rg_req);		return -ENOMEM;	}	rg_req[1] = SMP_REPORT_GENERAL;	for (i = 0; i < 5; i++) {		res = smp_execute_task(dev, rg_req, RG_REQ_SIZE, rg_resp,				       RG_RESP_SIZE);		if (res) {			SAS_DPRINTK("RG to ex %016llx failed:0x%x\n",				    SAS_ADDR(dev->sas_addr), res);			goto out;		} else if (rg_resp->result != SMP_RESP_FUNC_ACC) {			SAS_DPRINTK("RG:ex %016llx returned SMP result:0x%x\n",				    SAS_ADDR(dev->sas_addr), rg_resp->result);			res = rg_resp->result;			goto out;		}		ex_assign_report_general(dev, rg_resp);		if (dev->ex_dev.configuring) {			SAS_DPRINTK("RG: ex %llx self-configuring...\n",				    SAS_ADDR(dev->sas_addr));			schedule_timeout_interruptible(5*HZ);		} else			break;	}out:	kfree(rg_req);	kfree(rg_resp);	return res;}static void ex_assign_manuf_info(struct domain_device *dev, void					*_mi_resp){	u8 *mi_resp = _mi_resp;	struct sas_rphy *rphy = dev->rphy;	struct sas_expander_device *edev = rphy_to_expander_device(rphy);	memcpy(edev->vendor_id, mi_resp + 12, SAS_EXPANDER_VENDOR_ID_LEN);	memcpy(edev->product_id, mi_resp + 20, SAS_EXPANDER_PRODUCT_ID_LEN);	memcpy(edev->product_rev, mi_resp + 36,	       SAS_EXPANDER_PRODUCT_REV_LEN);	if (mi_resp[8] & 1) {		memcpy(edev->component_vendor_id, mi_resp + 40,		       SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN);		edev->component_id = mi_resp[48] << 8 | mi_resp[49];		edev->component_revision_id = mi_resp[50];	}}#define MI_REQ_SIZE   8#define MI_RESP_SIZE 64static int sas_ex_manuf_info(struct domain_device *dev){	u8 *mi_req;	u8 *mi_resp;	int res;	mi_req = alloc_smp_req(MI_REQ_SIZE);	if (!mi_req)		return -ENOMEM;	mi_resp = alloc_smp_resp(MI_RESP_SIZE);	if (!mi_resp) {		kfree(mi_req);		return -ENOMEM;	}	mi_req[1] = SMP_REPORT_MANUF_INFO;	res = smp_execute_task(dev, mi_req, MI_REQ_SIZE, mi_resp,MI_RESP_SIZE);	if (res) {		SAS_DPRINTK("MI: ex %016llx failed:0x%x\n",			    SAS_ADDR(dev->sas_addr), res);		goto out;	} else if (mi_resp[2] != SMP_RESP_FUNC_ACC) {		SAS_DPRINTK("MI ex %016llx returned SMP result:0x%x\n",			    SAS_ADDR(dev->sas_addr), mi_resp[2]);		goto out;	}	ex_assign_manuf_info(dev, mi_resp);out:	kfree(mi_req);	kfree(mi_resp);	return res;}#define PC_REQ_SIZE  44#define PC_RESP_SIZE 8int sas_smp_phy_control(struct domain_device *dev, int phy_id,			enum phy_func phy_func,			struct sas_phy_linkrates *rates){	u8 *pc_req;	u8 *pc_resp;	int res;	pc_req = alloc_smp_req(PC_REQ_SIZE);	if (!pc_req)		return -ENOMEM;	pc_resp = alloc_smp_resp(PC_RESP_SIZE);	if (!pc_resp) {		kfree(pc_req);		return -ENOMEM;	}	pc_req[1] = SMP_PHY_CONTROL;	pc_req[9] = phy_id;	pc_req[10]= phy_func;	if (rates) {		pc_req[32] = rates->minimum_linkrate << 4;		pc_req[33] = rates->maximum_linkrate << 4;	}	res = smp_execute_task(dev, pc_req, PC_REQ_SIZE, pc_resp,PC_RESP_SIZE);	kfree(pc_resp);	kfree(pc_req);	return res;}static void sas_ex_disable_phy(struct domain_device *dev, int phy_id){	struct expander_device *ex = &dev->ex_dev;	struct ex_phy *phy = &ex->ex_phy[phy_id];	sas_smp_phy_control(dev, phy_id, PHY_FUNC_DISABLE, NULL);	phy->linkrate = SAS_PHY_DISABLED;}static void sas_ex_disable_port(struct domain_device *dev, u8 *sas_addr){	struct expander_device *ex = &dev->ex_dev;	int i;	for (i = 0; i < ex->num_phys; i++) {		struct ex_phy *phy = &ex->ex_phy[i];		if (phy->phy_state == PHY_VACANT ||		    phy->phy_state == PHY_NOT_PRESENT)

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?