| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * driver for Microsemi PQI-based storage controllers |
|---|
| 3 | | - * Copyright (c) 2016-2017 Microsemi Corporation |
|---|
| 4 | + * Copyright (c) 2019-2020 Microchip Technology Inc. and its subsidiaries |
|---|
| 5 | + * Copyright (c) 2016-2018 Microsemi Corporation |
|---|
| 4 | 6 | * Copyright (c) 2016 PMC-Sierra, Inc. |
|---|
| 5 | 7 | * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 8 | | - * the Free Software Foundation; version 2 of the License. |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 11 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | | - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or |
|---|
| 13 | | - * NON INFRINGEMENT. See the GNU General Public License for more details. |
|---|
| 14 | | - * |
|---|
| 15 | | - * Questions/Comments/Bugfixes to esc.storagedev@microsemi.com |
|---|
| 8 | + * Questions/Comments/Bugfixes to storagedev@microchip.com |
|---|
| 16 | 9 | * |
|---|
| 17 | 10 | */ |
|---|
| 18 | 11 | |
|---|
| 19 | 12 | #include <linux/kernel.h> |
|---|
| 13 | +#include <linux/bsg-lib.h> |
|---|
| 20 | 14 | #include <scsi/scsi_host.h> |
|---|
| 21 | 15 | #include <scsi/scsi_cmnd.h> |
|---|
| 22 | 16 | #include <scsi/scsi_transport_sas.h> |
|---|
| 17 | +#include <asm/unaligned.h> |
|---|
| 23 | 18 | #include "smartpqi.h" |
|---|
| 24 | 19 | |
|---|
| 25 | 20 | static struct pqi_sas_phy *pqi_alloc_sas_phy(struct pqi_sas_port *pqi_sas_port) |
|---|
| .. | .. |
|---|
| 97 | 92 | |
|---|
| 98 | 93 | identify = &rphy->identify; |
|---|
| 99 | 94 | identify->sas_address = pqi_sas_port->sas_address; |
|---|
| 100 | | - identify->initiator_port_protocols = SAS_PROTOCOL_STP; |
|---|
| 101 | | - identify->target_port_protocols = SAS_PROTOCOL_STP; |
|---|
| 95 | + |
|---|
| 96 | + if (pqi_sas_port->device && |
|---|
| 97 | + pqi_sas_port->device->is_expander_smp_device) { |
|---|
| 98 | + identify->initiator_port_protocols = SAS_PROTOCOL_SMP; |
|---|
| 99 | + identify->target_port_protocols = SAS_PROTOCOL_SMP; |
|---|
| 100 | + } else { |
|---|
| 101 | + identify->initiator_port_protocols = SAS_PROTOCOL_STP; |
|---|
| 102 | + identify->target_port_protocols = SAS_PROTOCOL_STP; |
|---|
| 103 | + } |
|---|
| 102 | 104 | |
|---|
| 103 | 105 | return sas_rphy_add(rphy); |
|---|
| 104 | 106 | } |
|---|
| 105 | 107 | |
|---|
| 108 | +static struct sas_rphy *pqi_sas_rphy_alloc(struct pqi_sas_port *pqi_sas_port) |
|---|
| 109 | +{ |
|---|
| 110 | + if (pqi_sas_port->device && |
|---|
| 111 | + pqi_sas_port->device->is_expander_smp_device) |
|---|
| 112 | + return sas_expander_alloc(pqi_sas_port->port, |
|---|
| 113 | + SAS_FANOUT_EXPANDER_DEVICE); |
|---|
| 114 | + |
|---|
| 115 | + return sas_end_device_alloc(pqi_sas_port->port); |
|---|
| 116 | +} |
|---|
| 117 | + |
|---|
| 106 | 118 | static struct pqi_sas_port *pqi_alloc_sas_port( |
|---|
| 107 | | - struct pqi_sas_node *pqi_sas_node, u64 sas_address) |
|---|
| 119 | + struct pqi_sas_node *pqi_sas_node, u64 sas_address, |
|---|
| 120 | + struct pqi_scsi_dev *device) |
|---|
| 108 | 121 | { |
|---|
| 109 | 122 | int rc; |
|---|
| 110 | 123 | struct pqi_sas_port *pqi_sas_port; |
|---|
| .. | .. |
|---|
| 127 | 140 | |
|---|
| 128 | 141 | pqi_sas_port->port = port; |
|---|
| 129 | 142 | pqi_sas_port->sas_address = sas_address; |
|---|
| 143 | + pqi_sas_port->device = device; |
|---|
| 130 | 144 | list_add_tail(&pqi_sas_port->port_list_entry, |
|---|
| 131 | 145 | &pqi_sas_node->port_list_head); |
|---|
| 132 | 146 | |
|---|
| .. | .. |
|---|
| 146 | 160 | struct pqi_sas_phy *next; |
|---|
| 147 | 161 | |
|---|
| 148 | 162 | list_for_each_entry_safe(pqi_sas_phy, next, |
|---|
| 149 | | - &pqi_sas_port->phy_list_head, phy_list_entry) |
|---|
| 163 | + &pqi_sas_port->phy_list_head, phy_list_entry) |
|---|
| 150 | 164 | pqi_free_sas_phy(pqi_sas_phy); |
|---|
| 151 | 165 | |
|---|
| 152 | 166 | sas_port_delete(pqi_sas_port->port); |
|---|
| .. | .. |
|---|
| 176 | 190 | return; |
|---|
| 177 | 191 | |
|---|
| 178 | 192 | list_for_each_entry_safe(pqi_sas_port, next, |
|---|
| 179 | | - &pqi_sas_node->port_list_head, port_list_entry) |
|---|
| 193 | + &pqi_sas_node->port_list_head, port_list_entry) |
|---|
| 180 | 194 | pqi_free_sas_port(pqi_sas_port); |
|---|
| 181 | 195 | |
|---|
| 182 | 196 | kfree(pqi_sas_node); |
|---|
| .. | .. |
|---|
| 206 | 220 | struct pqi_sas_port *pqi_sas_port; |
|---|
| 207 | 221 | struct pqi_sas_phy *pqi_sas_phy; |
|---|
| 208 | 222 | |
|---|
| 209 | | - parent_dev = &shost->shost_gendev; |
|---|
| 223 | + parent_dev = &shost->shost_dev; |
|---|
| 210 | 224 | |
|---|
| 211 | 225 | pqi_sas_node = pqi_alloc_sas_node(parent_dev); |
|---|
| 212 | 226 | if (!pqi_sas_node) |
|---|
| 213 | 227 | return -ENOMEM; |
|---|
| 214 | 228 | |
|---|
| 215 | | - pqi_sas_port = pqi_alloc_sas_port(pqi_sas_node, ctrl_info->sas_address); |
|---|
| 229 | + pqi_sas_port = pqi_alloc_sas_port(pqi_sas_node, |
|---|
| 230 | + ctrl_info->sas_address, NULL); |
|---|
| 216 | 231 | if (!pqi_sas_port) { |
|---|
| 217 | 232 | rc = -ENODEV; |
|---|
| 218 | 233 | goto free_sas_node; |
|---|
| .. | .. |
|---|
| 254 | 269 | struct pqi_sas_port *pqi_sas_port; |
|---|
| 255 | 270 | struct sas_rphy *rphy; |
|---|
| 256 | 271 | |
|---|
| 257 | | - pqi_sas_port = pqi_alloc_sas_port(pqi_sas_node, device->sas_address); |
|---|
| 272 | + pqi_sas_port = pqi_alloc_sas_port(pqi_sas_node, |
|---|
| 273 | + device->sas_address, device); |
|---|
| 258 | 274 | if (!pqi_sas_port) |
|---|
| 259 | 275 | return -ENOMEM; |
|---|
| 260 | 276 | |
|---|
| 261 | | - rphy = sas_end_device_alloc(pqi_sas_port->port); |
|---|
| 277 | + rphy = pqi_sas_rphy_alloc(pqi_sas_port); |
|---|
| 262 | 278 | if (!rphy) { |
|---|
| 263 | 279 | rc = -ENODEV; |
|---|
| 264 | 280 | goto free_sas_port; |
|---|
| .. | .. |
|---|
| 296 | 312 | static int pqi_sas_get_enclosure_identifier(struct sas_rphy *rphy, |
|---|
| 297 | 313 | u64 *identifier) |
|---|
| 298 | 314 | { |
|---|
| 299 | | - return 0; |
|---|
| 315 | + int rc; |
|---|
| 316 | + unsigned long flags; |
|---|
| 317 | + struct Scsi_Host *shost; |
|---|
| 318 | + struct pqi_ctrl_info *ctrl_info; |
|---|
| 319 | + struct pqi_scsi_dev *found_device; |
|---|
| 320 | + struct pqi_scsi_dev *device; |
|---|
| 321 | + |
|---|
| 322 | + if (!rphy) |
|---|
| 323 | + return -ENODEV; |
|---|
| 324 | + |
|---|
| 325 | + shost = rphy_to_shost(rphy); |
|---|
| 326 | + ctrl_info = shost_to_hba(shost); |
|---|
| 327 | + spin_lock_irqsave(&ctrl_info->scsi_device_list_lock, flags); |
|---|
| 328 | + found_device = pqi_find_device_by_sas_rphy(ctrl_info, rphy); |
|---|
| 329 | + |
|---|
| 330 | + if (!found_device) { |
|---|
| 331 | + rc = -ENODEV; |
|---|
| 332 | + goto out; |
|---|
| 333 | + } |
|---|
| 334 | + |
|---|
| 335 | + if (found_device->devtype == TYPE_ENCLOSURE) { |
|---|
| 336 | + *identifier = get_unaligned_be64(&found_device->wwid); |
|---|
| 337 | + rc = 0; |
|---|
| 338 | + goto out; |
|---|
| 339 | + } |
|---|
| 340 | + |
|---|
| 341 | + if (found_device->box_index == 0xff || |
|---|
| 342 | + found_device->phys_box_on_bus == 0 || |
|---|
| 343 | + found_device->bay == 0xff) { |
|---|
| 344 | + rc = -EINVAL; |
|---|
| 345 | + goto out; |
|---|
| 346 | + } |
|---|
| 347 | + |
|---|
| 348 | + list_for_each_entry(device, &ctrl_info->scsi_device_list, |
|---|
| 349 | + scsi_device_list_entry) { |
|---|
| 350 | + if (device->devtype == TYPE_ENCLOSURE && |
|---|
| 351 | + device->box_index == found_device->box_index && |
|---|
| 352 | + device->phys_box_on_bus == |
|---|
| 353 | + found_device->phys_box_on_bus && |
|---|
| 354 | + memcmp(device->phys_connector, |
|---|
| 355 | + found_device->phys_connector, 2) == 0) { |
|---|
| 356 | + *identifier = |
|---|
| 357 | + get_unaligned_be64(&device->wwid); |
|---|
| 358 | + rc = 0; |
|---|
| 359 | + goto out; |
|---|
| 360 | + } |
|---|
| 361 | + } |
|---|
| 362 | + |
|---|
| 363 | + if (found_device->phy_connected_dev_type != SA_DEVICE_TYPE_CONTROLLER) { |
|---|
| 364 | + rc = -EINVAL; |
|---|
| 365 | + goto out; |
|---|
| 366 | + } |
|---|
| 367 | + |
|---|
| 368 | + list_for_each_entry(device, &ctrl_info->scsi_device_list, |
|---|
| 369 | + scsi_device_list_entry) { |
|---|
| 370 | + if (device->devtype == TYPE_ENCLOSURE && |
|---|
| 371 | + CISS_GET_DRIVE_NUMBER(device->scsi3addr) == |
|---|
| 372 | + PQI_VSEP_CISS_BTL) { |
|---|
| 373 | + *identifier = get_unaligned_be64(&device->wwid); |
|---|
| 374 | + rc = 0; |
|---|
| 375 | + goto out; |
|---|
| 376 | + } |
|---|
| 377 | + } |
|---|
| 378 | + |
|---|
| 379 | + rc = -EINVAL; |
|---|
| 380 | +out: |
|---|
| 381 | + spin_unlock_irqrestore(&ctrl_info->scsi_device_list_lock, flags); |
|---|
| 382 | + |
|---|
| 383 | + return rc; |
|---|
| 300 | 384 | } |
|---|
| 301 | 385 | |
|---|
| 302 | 386 | static int pqi_sas_get_bay_identifier(struct sas_rphy *rphy) |
|---|
| 303 | 387 | { |
|---|
| 304 | | - return -ENXIO; |
|---|
| 388 | + int rc; |
|---|
| 389 | + unsigned long flags; |
|---|
| 390 | + struct pqi_ctrl_info *ctrl_info; |
|---|
| 391 | + struct pqi_scsi_dev *device; |
|---|
| 392 | + struct Scsi_Host *shost; |
|---|
| 393 | + |
|---|
| 394 | + if (!rphy) |
|---|
| 395 | + return -ENODEV; |
|---|
| 396 | + |
|---|
| 397 | + shost = rphy_to_shost(rphy); |
|---|
| 398 | + ctrl_info = shost_to_hba(shost); |
|---|
| 399 | + spin_lock_irqsave(&ctrl_info->scsi_device_list_lock, flags); |
|---|
| 400 | + device = pqi_find_device_by_sas_rphy(ctrl_info, rphy); |
|---|
| 401 | + |
|---|
| 402 | + if (!device) { |
|---|
| 403 | + rc = -ENODEV; |
|---|
| 404 | + goto out; |
|---|
| 405 | + } |
|---|
| 406 | + |
|---|
| 407 | + if (device->bay == 0xff) |
|---|
| 408 | + rc = -EINVAL; |
|---|
| 409 | + else |
|---|
| 410 | + rc = device->bay; |
|---|
| 411 | + |
|---|
| 412 | +out: |
|---|
| 413 | + spin_unlock_irqrestore(&ctrl_info->scsi_device_list_lock, flags); |
|---|
| 414 | + |
|---|
| 415 | + return rc; |
|---|
| 305 | 416 | } |
|---|
| 306 | 417 | |
|---|
| 307 | 418 | static int pqi_sas_phy_reset(struct sas_phy *phy, int hard_reset) |
|---|
| .. | .. |
|---|
| 329 | 440 | return -EINVAL; |
|---|
| 330 | 441 | } |
|---|
| 331 | 442 | |
|---|
| 443 | +#define CSMI_IOCTL_TIMEOUT 60 |
|---|
| 444 | +#define SMP_CRC_FIELD_LENGTH 4 |
|---|
| 445 | + |
|---|
| 446 | +static struct bmic_csmi_smp_passthru_buffer * |
|---|
| 447 | +pqi_build_csmi_smp_passthru_buffer(struct sas_rphy *rphy, |
|---|
| 448 | + struct bsg_job *job) |
|---|
| 449 | +{ |
|---|
| 450 | + struct bmic_csmi_smp_passthru_buffer *smp_buf; |
|---|
| 451 | + struct bmic_csmi_ioctl_header *ioctl_header; |
|---|
| 452 | + struct bmic_csmi_smp_passthru *parameters; |
|---|
| 453 | + u32 req_size; |
|---|
| 454 | + u32 resp_size; |
|---|
| 455 | + |
|---|
| 456 | + smp_buf = kzalloc(sizeof(*smp_buf), GFP_KERNEL); |
|---|
| 457 | + if (!smp_buf) |
|---|
| 458 | + return NULL; |
|---|
| 459 | + |
|---|
| 460 | + req_size = job->request_payload.payload_len; |
|---|
| 461 | + resp_size = job->reply_payload.payload_len; |
|---|
| 462 | + |
|---|
| 463 | + ioctl_header = &smp_buf->ioctl_header; |
|---|
| 464 | + put_unaligned_le32(sizeof(smp_buf->ioctl_header), |
|---|
| 465 | + &ioctl_header->header_length); |
|---|
| 466 | + put_unaligned_le32(CSMI_IOCTL_TIMEOUT, &ioctl_header->timeout); |
|---|
| 467 | + put_unaligned_le32(CSMI_CC_SAS_SMP_PASSTHRU, |
|---|
| 468 | + &ioctl_header->control_code); |
|---|
| 469 | + put_unaligned_le32(sizeof(smp_buf->parameters), &ioctl_header->length); |
|---|
| 470 | + |
|---|
| 471 | + parameters = &smp_buf->parameters; |
|---|
| 472 | + parameters->phy_identifier = rphy->identify.phy_identifier; |
|---|
| 473 | + parameters->port_identifier = 0; |
|---|
| 474 | + parameters->connection_rate = 0; |
|---|
| 475 | + put_unaligned_be64(rphy->identify.sas_address, |
|---|
| 476 | + ¶meters->destination_sas_address); |
|---|
| 477 | + |
|---|
| 478 | + if (req_size > SMP_CRC_FIELD_LENGTH) |
|---|
| 479 | + req_size -= SMP_CRC_FIELD_LENGTH; |
|---|
| 480 | + |
|---|
| 481 | + put_unaligned_le32(req_size, ¶meters->request_length); |
|---|
| 482 | + put_unaligned_le32(resp_size, ¶meters->response_length); |
|---|
| 483 | + |
|---|
| 484 | + sg_copy_to_buffer(job->request_payload.sg_list, |
|---|
| 485 | + job->reply_payload.sg_cnt, ¶meters->request, |
|---|
| 486 | + req_size); |
|---|
| 487 | + |
|---|
| 488 | + return smp_buf; |
|---|
| 489 | +} |
|---|
| 490 | + |
|---|
| 491 | +static unsigned int pqi_build_sas_smp_handler_reply( |
|---|
| 492 | + struct bmic_csmi_smp_passthru_buffer *smp_buf, struct bsg_job *job, |
|---|
| 493 | + struct pqi_raid_error_info *error_info) |
|---|
| 494 | +{ |
|---|
| 495 | + sg_copy_from_buffer(job->reply_payload.sg_list, |
|---|
| 496 | + job->reply_payload.sg_cnt, &smp_buf->parameters.response, |
|---|
| 497 | + le32_to_cpu(smp_buf->parameters.response_length)); |
|---|
| 498 | + |
|---|
| 499 | + job->reply_len = le16_to_cpu(error_info->sense_data_length); |
|---|
| 500 | + memcpy(job->reply, error_info->data, |
|---|
| 501 | + le16_to_cpu(error_info->sense_data_length)); |
|---|
| 502 | + |
|---|
| 503 | + return job->reply_payload.payload_len - |
|---|
| 504 | + get_unaligned_le32(&error_info->data_in_transferred); |
|---|
| 505 | +} |
|---|
| 506 | + |
|---|
| 507 | +void pqi_sas_smp_handler(struct bsg_job *job, struct Scsi_Host *shost, |
|---|
| 508 | + struct sas_rphy *rphy) |
|---|
| 509 | +{ |
|---|
| 510 | + int rc; |
|---|
| 511 | + struct pqi_ctrl_info *ctrl_info; |
|---|
| 512 | + struct bmic_csmi_smp_passthru_buffer *smp_buf; |
|---|
| 513 | + struct pqi_raid_error_info error_info; |
|---|
| 514 | + unsigned int reslen = 0; |
|---|
| 515 | + |
|---|
| 516 | + ctrl_info = shost_to_hba(shost); |
|---|
| 517 | + |
|---|
| 518 | + if (job->reply_payload.payload_len == 0) { |
|---|
| 519 | + rc = -ENOMEM; |
|---|
| 520 | + goto out; |
|---|
| 521 | + } |
|---|
| 522 | + |
|---|
| 523 | + if (!rphy) { |
|---|
| 524 | + rc = -EINVAL; |
|---|
| 525 | + goto out; |
|---|
| 526 | + } |
|---|
| 527 | + |
|---|
| 528 | + if (rphy->identify.device_type != SAS_FANOUT_EXPANDER_DEVICE) { |
|---|
| 529 | + rc = -EINVAL; |
|---|
| 530 | + goto out; |
|---|
| 531 | + } |
|---|
| 532 | + |
|---|
| 533 | + if (job->request_payload.sg_cnt > 1 || job->reply_payload.sg_cnt > 1) { |
|---|
| 534 | + rc = -EINVAL; |
|---|
| 535 | + goto out; |
|---|
| 536 | + } |
|---|
| 537 | + |
|---|
| 538 | + smp_buf = pqi_build_csmi_smp_passthru_buffer(rphy, job); |
|---|
| 539 | + if (!smp_buf) { |
|---|
| 540 | + rc = -ENOMEM; |
|---|
| 541 | + goto out; |
|---|
| 542 | + } |
|---|
| 543 | + |
|---|
| 544 | + rc = pqi_csmi_smp_passthru(ctrl_info, smp_buf, sizeof(*smp_buf), |
|---|
| 545 | + &error_info); |
|---|
| 546 | + if (rc) |
|---|
| 547 | + goto out; |
|---|
| 548 | + |
|---|
| 549 | + reslen = pqi_build_sas_smp_handler_reply(smp_buf, job, &error_info); |
|---|
| 550 | +out: |
|---|
| 551 | + bsg_job_done(job, rc, reslen); |
|---|
| 552 | + pqi_ctrl_unbusy(ctrl_info); |
|---|
| 553 | +} |
|---|
| 332 | 554 | struct sas_function_template pqi_sas_transport_functions = { |
|---|
| 333 | 555 | .get_linkerrors = pqi_sas_get_linkerrors, |
|---|
| 334 | 556 | .get_enclosure_identifier = pqi_sas_get_enclosure_identifier, |
|---|
| .. | .. |
|---|
| 338 | 560 | .phy_setup = pqi_sas_phy_setup, |
|---|
| 339 | 561 | .phy_release = pqi_sas_phy_release, |
|---|
| 340 | 562 | .set_phy_speed = pqi_sas_phy_speed, |
|---|
| 563 | + .smp_handler = pqi_sas_smp_handler, |
|---|
| 341 | 564 | }; |
|---|