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 + -
显示快捷键?