blob: b0d1cdcfd51a46239bf354a0a02a2985161e04bf [file] [log] [blame] [edit]
/*
* sg_rdac
*
* Retrieve / set RDAC options.
*
* Copyright (C) 2006-2018 Hannes Reinecke <[email protected]>
*
* Based on sg_modes.c and sg_emc_trespass.c; credits from there apply.
*
* 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, or (at your option)
* any later version.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sg_lib.h"
#include "sg_cmds_basic.h"
#include "sg_unaligned.h"
#include "sg_pr2serr.h"
static const char * version_str = "1.17 20180512";
uint8_t mode6_hdr[] = {
0x75, /* Length */
0, /* medium */
0, /* params */
8, /* Block descriptor length */
};
uint8_t mode10_hdr[] = {
0x01, 0x18, /* Length */
0, /* medium */
0, /* params */
0, 0, /* reserved */
0, 0, /* block descriptor length */
};
uint8_t block_descriptor[] = {
0, /* Density code */
0, 0, 0, /* Number of blocks */
0, /* Reserved */
0, 0x02, 0, /* 512 byte blocks */
};
struct rdac_page_common {
uint8_t current_serial[16];
uint8_t alternate_serial[16];
uint8_t current_mode_msb;
uint8_t current_mode_lsb;
uint8_t alternate_mode_msb;
uint8_t alternate_mode_lsb;
uint8_t quiescence;
uint8_t options;
};
struct rdac_legacy_page {
uint8_t page_code;
uint8_t page_length;
struct rdac_page_common attr;
uint8_t lun_table[32];
uint8_t lun_table_exp[32];
unsigned short reserved;
};
struct rdac_expanded_page {
uint8_t page_code;
uint8_t subpage_code;
uint8_t page_length[2];
struct rdac_page_common attr;
uint8_t lun_table[256];
uint8_t reserved[2];
};
static int do_verbose = 0;
static void dump_mode_page( uint8_t *page, int len )
{
int i, k;
for (k = 0; k < len; k += 16) {
printf("%x:",k / 16);
for (i = 0; i < 16; i++) {
printf(" %02x", page[k + i]);
if (k + i >= len) {
printf("\n");
break;
}
}
printf("\n");
}
}
#define MX_ALLOC_LEN (1024 * 4)
#define RDAC_CONTROLLER_PAGE 0x2c
#define RDAC_CONTROLLER_PAGE_LEN 0x68
#define LEGACY_PAGE 0x00
#define EXPANDED_LUN_SPACE_PAGE 0x01
#define EXPANDED_LUN_SPACE_PAGE_LEN 0x128
#define RDAC_FAIL_ALL_PATHS 0x1
#define RDAC_FAIL_SELECTED_PATHS 0x2
#define RDAC_FORCE_QUIESCENCE 0x2
#define RDAC_QUIESCENCE_TIME 10
static int fail_all_paths(int fd, bool use_6_byte)
{
struct rdac_legacy_page *rdac_page;
struct rdac_expanded_page *rdac_page_exp;
struct rdac_page_common *rdac_common = NULL;
uint8_t fail_paths_pg[308];
int res;
char b[80];
memset(fail_paths_pg, 0, 308);
if (use_6_byte) {
memcpy(fail_paths_pg, mode6_hdr, 4);
memcpy(fail_paths_pg + 4, block_descriptor, 8);
rdac_page = (struct rdac_legacy_page *)(fail_paths_pg + 4 + 8);
rdac_page->page_code = RDAC_CONTROLLER_PAGE;
rdac_page->page_length = RDAC_CONTROLLER_PAGE_LEN;
rdac_common = &rdac_page->attr;
} else {
memcpy(fail_paths_pg, mode10_hdr, 8);
rdac_page_exp = (struct rdac_expanded_page *)
(fail_paths_pg + 8);
rdac_page_exp->page_code = RDAC_CONTROLLER_PAGE | 0x40;
rdac_page_exp->subpage_code = 0x1;
sg_put_unaligned_be16(EXPANDED_LUN_SPACE_PAGE_LEN,
rdac_page_exp->page_length + 0);
rdac_common = &rdac_page_exp->attr;
}
rdac_common->current_mode_lsb = RDAC_FAIL_ALL_PATHS;
rdac_common->quiescence = RDAC_QUIESCENCE_TIME;
rdac_common->options = RDAC_FORCE_QUIESCENCE;
if (use_6_byte) {
res = sg_ll_mode_select6(fd, 1 /* pf */, 0 /* sp */,
fail_paths_pg, 118,
true, (do_verbose ? 2 : 0));
} else {
res = sg_ll_mode_select10(fd, 1 /* pf */, 0 /* sp */,
fail_paths_pg, 308,
true, (do_verbose ? 2: 0));
}
switch (res) {
case 0:
if (do_verbose)
pr2serr("fail paths successful\n");
break;
default:
sg_get_category_sense_str(res, sizeof(b), b, do_verbose);
pr2serr("fail paths failed: %s\n", b);
break;
}
return res;
}
static int fail_this_path(int fd, int lun, bool use_6_byte)
{
int res;
struct rdac_legacy_page *rdac_page;
struct rdac_expanded_page *rdac_page_exp;
struct rdac_page_common *rdac_common = NULL;
uint8_t fail_paths_pg[308];
char b[80];
if (use_6_byte) {
if (lun > 31) {
pr2serr("must use 10 byte cdb to fail luns over 31\n");
return -1;
}
} else { /* 10 byte cdb case */
if (lun > 255) {
pr2serr("lun cannot exceed 255\n");
return -1;
}
}
memset(fail_paths_pg, 0, 308);
if (use_6_byte) {
memcpy(fail_paths_pg, mode6_hdr, 4);
memcpy(fail_paths_pg + 4, block_descriptor, 8);
rdac_page = (struct rdac_legacy_page *)(fail_paths_pg + 4 + 8);
rdac_page->page_code = RDAC_CONTROLLER_PAGE;
rdac_page->page_length = RDAC_CONTROLLER_PAGE_LEN;
rdac_common = &rdac_page->attr;
memset(rdac_page->lun_table, 0x0, 32);
rdac_page->lun_table[lun] = 0x81;
} else {
memcpy(fail_paths_pg, mode10_hdr, 8);
rdac_page_exp = (struct rdac_expanded_page *)
(fail_paths_pg + 8);
rdac_page_exp->page_code = RDAC_CONTROLLER_PAGE | 0x40;
rdac_page_exp->subpage_code = 0x1;
sg_put_unaligned_be16(EXPANDED_LUN_SPACE_PAGE_LEN,
rdac_page_exp->page_length + 0);
rdac_common = &rdac_page_exp->attr;
memset(rdac_page_exp->lun_table, 0x0, 256);
rdac_page_exp->lun_table[lun] = 0x81;
}
rdac_common->current_mode_lsb = RDAC_FAIL_SELECTED_PATHS;
rdac_common->quiescence = RDAC_QUIESCENCE_TIME;
rdac_common->options = RDAC_FORCE_QUIESCENCE;
if (use_6_byte) {
res = sg_ll_mode_select6(fd, 1 /* pf */, 0 /* sp */,
fail_paths_pg, 118,
true, (do_verbose ? 2 : 0));
} else {
res = sg_ll_mode_select10(fd, 1 /* pf */, 0 /* sp */,
fail_paths_pg, 308,
true, (do_verbose ? 2: 0));
}
switch (res) {
case 0:
if (do_verbose)
pr2serr("fail paths successful\n");
break;
default:
sg_get_category_sense_str(res, sizeof(b), b, do_verbose);
pr2serr("fail paths page (lun=%d) failed: %s\n", lun, b);
break;
}
return res;
}
static void print_rdac_mode(uint8_t *ptr, bool exp_subpg)
{
int i, k, bd_len, lun_table_len;
uint8_t * lun_table = NULL;
struct rdac_legacy_page *legacy;
struct rdac_expanded_page *expanded;
struct rdac_page_common *rdac_ptr = NULL;
if (exp_subpg) {
bd_len = ptr[7];
expanded = (struct rdac_expanded_page *)(ptr + 8 + bd_len);
rdac_ptr = &expanded->attr;
lun_table = expanded->lun_table;
lun_table_len = 256;
} else {
bd_len = ptr[3];
legacy = (struct rdac_legacy_page *)(ptr + 4 + bd_len);
rdac_ptr = &legacy->attr;
lun_table = legacy->lun_table;
lun_table_len = 32;
}
printf("RDAC %s page\n", exp_subpg ? "Expanded" : "Legacy");
printf(" Controller serial: %s\n",
rdac_ptr->current_serial);
printf(" Alternate controller serial: %s\n",
rdac_ptr->alternate_serial);
printf(" RDAC mode (redundant processor): ");
switch (rdac_ptr->current_mode_msb) {
case 0x00:
printf("alternate controller not present; ");
break;
case 0x01:
printf("alternate controller present; ");
break;
default:
printf("(Unknown controller status 0x%x); ",
rdac_ptr->current_mode_msb);
break;
}
switch (rdac_ptr->current_mode_lsb) {
case 0x0:
printf("inactive\n");
break;
case 0x1:
printf("active\n");
break;
case 0x2:
printf("Dual active mode\n");
break;
default:
printf("(Unknown mode 0x%x)\n",
rdac_ptr->current_mode_lsb);
}
printf(" RDAC mode (alternate processor): ");
switch (rdac_ptr->alternate_mode_msb) {
case 0x00:
printf("alternate controller not present; ");
break;
case 0x01:
printf("alternate controller present; ");
break;
default:
printf("(Unknown status 0x%x); ",
rdac_ptr->alternate_mode_msb);
break;
}
switch (rdac_ptr->alternate_mode_lsb) {
case 0x0:
printf("inactive\n");
break;
case 0x1:
printf("active\n");
break;
case 0x2:
printf("Dual active mode\n");
break;
case 0x3:
printf("Not present\n");
break;
case 0x4:
printf("held in reset\n");
break;
default:
printf("(Unknown mode 0x%x)\n",
rdac_ptr->alternate_mode_lsb);
}
printf(" Quiescence timeout: %d\n", rdac_ptr->quiescence);
printf(" RDAC option 0x%x\n", rdac_ptr->options);
printf(" ALUA: %s\n", (rdac_ptr->options & 0x4 ? "Enabled" :
"Disabled" ));
printf(" Force Quiescence: %s\n", (rdac_ptr->options & 0x2 ?
"Enabled" : "Disabled" ));
printf (" LUN Table: (p = preferred, a = alternate, u = utm lun)\n");
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
for (k = 0; k < lun_table_len; k += 16) {
printf(" 0x%x:",k / 16);
for (i = 0; i < 16; i++) {
switch (lun_table[k + i]) {
case 0x0:
printf(" x");
break;
case 0x1:
printf(" p");
break;
case 0x2:
printf(" a");
break;
case 0x3:
printf(" u");
break;
default:
printf(" ?");
break;
}
if (i == 7) {
printf(" ");
}
}
printf("\n");
}
}
static void usage()
{
printf("Usage: sg_rdac [-6] [-a] [-f=LUN] [-v] [-V] DEVICE\n"
" where:\n"
" -6 use 6 byte cdbs for mode sense/select\n"
" -a transfer all devices to the controller\n"
" serving DEVICE.\n"
" -f=LUN transfer the device at LUN to the\n"
" controller serving DEVICE\n"
" -v verbose\n"
" -V print version then exit\n\n"
" Display/Modify RDAC Redundant Controller Page 0x2c.\n"
" If [-a] or [-f] is not specified the current settings"
" are displayed.\n");
}
int main(int argc, char * argv[])
{
bool fail_all = false;
bool fail_path = false;
bool use_6_byte = false;
int res, fd, k, resid, len, lun = -1;
int ret = 0;
char **argptr;
char * file_name = 0;
uint8_t rsp_buff[MX_ALLOC_LEN];
if (argc < 2) {
usage ();
return SG_LIB_SYNTAX_ERROR;
}
for (k = 1; k < argc; ++k) {
argptr = argv + k;
if (!strcmp (*argptr, "-v"))
++do_verbose;
else if (!strncmp(*argptr, "-f=",3)) {
fail_path = true;
lun = strtoul(*argptr + 3, NULL, 0);
}
else if (!strcmp(*argptr, "-a")) {
fail_all = true;
}
else if (!strcmp(*argptr, "-6")) {
use_6_byte = true;
}
else if (!strcmp(*argptr, "-V")) {
pr2serr("sg_rdac version: %s\n", version_str);
return 0;
}
else if (*argv[k] == '-') {
pr2serr("Unrecognized switch: %s\n", argv[k]);
file_name = 0;
break;
}
else if (0 == file_name)
file_name = argv[k];
else {
pr2serr("too many arguments\n");
file_name = 0;
break;
}
}
if (0 == file_name) {
usage();
return SG_LIB_SYNTAX_ERROR;
}
fd = sg_cmds_open_device(file_name, false /* rw */, do_verbose);
if (fd < 0) {
pr2serr("open error: %s: %s\n", file_name, safe_strerror(-fd));
usage();
ret = sg_convert_errno(-fd);
goto fini;
}
if (fail_all) {
res = fail_all_paths(fd, use_6_byte);
} else if (fail_path) {
res = fail_this_path(fd, lun, use_6_byte);
} else {
resid = 0;
if (use_6_byte)
res = sg_ll_mode_sense6(fd, /* DBD */ false,
/* PC */ 0,
0x2c /* page */,
0 /*subpage */,
rsp_buff, 252,
true, do_verbose);
else
res = sg_ll_mode_sense10_v2(fd, /* llbaa */ false,
/* DBD */ false,
/* page control */0,
0x2c, 0x1 /* subpage */,
rsp_buff, 308, 0, &resid,
true, do_verbose);
if (! res) {
len = sg_msense_calc_length(rsp_buff, 308, use_6_byte,
NULL);
if (resid > 0) {
len = ((308 - resid) < len) ? (308 - resid) :
len;
if (len < 2)
pr2serr("MS(10) residual value (%d) "
"a worry\n", resid);
}
if (do_verbose && (len > 1))
dump_mode_page(rsp_buff, len);
print_rdac_mode(rsp_buff, ! use_6_byte);
} else {
if (SG_LIB_CAT_INVALID_OP == res)
pr2serr(">>>>>> try again without the '-6' "
"switch for a 10 byte MODE SENSE "
"command\n");
else if (SG_LIB_CAT_ILLEGAL_REQ == res)
pr2serr("mode sense: invalid field in cdb "
"(perhaps subpages or page control "
"(PC) not supported)\n");
else {
char b[80];
sg_get_category_sense_str(res, sizeof(b), b,
do_verbose);
pr2serr("mode sense failed: %s\n", b);
}
}
}
ret = res;
res = sg_cmds_close_device(fd);
if (res < 0) {
pr2serr("close error: %s\n", safe_strerror(-res));
if (0 == ret)
ret = sg_convert_errno(res);
}
fini:
if (0 == do_verbose) {
if (! sg_if_can2stderr("sg_rdac failed: ", ret))
pr2serr("Some error occurred, try again with '-v' "
"or '-vv' for more information\n");
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}