mtip32xx: Add new sysfs entry 'status'

* Add support for detecting the following device status
        - write protect
        - over temp (thermal shutdown)
* Add new sysfs entry 'status', possible values - online, write_protect, thermal_shutdown
* Add new file 'sysfs-block-rssd' to document ABI (Reported-by: Greg Kroah-Hartman)

Signed-off-by: Asai Thambi S P <asamymuthupa@micron.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Asai Thambi S P 2012-04-09 08:35:38 +02:00 committed by Jens Axboe
parent dad40f16ff
commit f65872177d
3 changed files with 331 additions and 29 deletions

View File

@ -0,0 +1,18 @@
What: /sys/block/rssd*/registers
Date: March 2012
KernelVersion: 3.3
Contact: Asai Thambi S P <asamymuthupa@micron.com>
Description: This is a read-only file. Dumps below driver information and
hardware registers.
- S ACTive
- Command Issue
- Allocated
- Completed
- PORT IRQ STAT
- HOST IRQ STAT
What: /sys/block/rssd*/status
Date: April 2012
KernelVersion: 3.4
Contact: Asai Thambi S P <asamymuthupa@micron.com>
Description: This is a read-only file. Indicates the status of the device.

View File

@ -725,6 +725,10 @@ static void print_tags(struct driver_data *dd,
dev_info(&dd->pdev->dev, "%s [%i tags]\n", msg, count); dev_info(&dd->pdev->dev, "%s [%i tags]\n", msg, count);
} }
static int mtip_read_log_page(struct mtip_port *port, u8 page, u16 *buffer,
dma_addr_t buffer_dma, unsigned int sectors);
static int mtip_get_smart_attr(struct mtip_port *port, unsigned int id,
struct smart_attr *attrib);
/* /*
* Handle an error. * Handle an error.
* *
@ -735,12 +739,15 @@ static void print_tags(struct driver_data *dd,
*/ */
static void mtip_handle_tfe(struct driver_data *dd) static void mtip_handle_tfe(struct driver_data *dd)
{ {
int group, tag, bit, reissue; int group, tag, bit, reissue, rv;
struct mtip_port *port; struct mtip_port *port;
struct mtip_cmd *command; struct mtip_cmd *cmd;
u32 completed; u32 completed;
struct host_to_dev_fis *fis; struct host_to_dev_fis *fis;
unsigned long tagaccum[SLOTBITS_IN_LONGS]; unsigned long tagaccum[SLOTBITS_IN_LONGS];
unsigned char *buf;
char *fail_reason = NULL;
int fail_all_ncq_write = 0, fail_all_ncq_cmds = 0;
dev_warn(&dd->pdev->dev, "Taskfile error\n"); dev_warn(&dd->pdev->dev, "Taskfile error\n");
@ -772,13 +779,13 @@ static void mtip_handle_tfe(struct driver_data *dd)
if (tag == MTIP_TAG_INTERNAL) if (tag == MTIP_TAG_INTERNAL)
continue; continue;
command = &port->commands[tag]; cmd = &port->commands[tag];
if (likely(command->comp_func)) { if (likely(cmd->comp_func)) {
set_bit(tag, tagaccum); set_bit(tag, tagaccum);
atomic_set(&port->commands[tag].active, 0); atomic_set(&cmd->active, 0);
command->comp_func(port, cmd->comp_func(port,
tag, tag,
command->comp_data, cmd->comp_data,
0); 0);
} else { } else {
dev_err(&port->dd->pdev->dev, dev_err(&port->dd->pdev->dev,
@ -798,6 +805,38 @@ static void mtip_handle_tfe(struct driver_data *dd)
mdelay(20); mdelay(20);
mtip_restart_port(port); mtip_restart_port(port);
/* Trying to determine the cause of the error */
rv = mtip_read_log_page(dd->port, ATA_LOG_SATA_NCQ,
dd->port->log_buf,
dd->port->log_buf_dma, 1);
if (rv) {
dev_warn(&dd->pdev->dev,
"Error in READ LOG EXT (10h) command\n");
/* non-critical error, don't fail the load */
} else {
buf = (unsigned char *)dd->port->log_buf;
if (buf[259] & 0x1) {
dev_info(&dd->pdev->dev,
"Write protect bit is set.\n");
set_bit(MTIP_DD_FLAG_WRITE_PROTECT_BIT, &dd->dd_flag);
fail_all_ncq_write = 1;
fail_reason = "write protect";
}
if (buf[288] == 0xF7) {
dev_info(&dd->pdev->dev,
"Exceeded Tmax, drive in thermal shutdown.\n");
set_bit(MTIP_DD_FLAG_OVER_TEMP_BIT, &dd->dd_flag);
fail_all_ncq_cmds = 1;
fail_reason = "thermal shutdown";
}
if (buf[288] == 0xBF) {
dev_info(&dd->pdev->dev,
"Drive indicates rebuild has failed.\n");
fail_all_ncq_cmds = 1;
fail_reason = "rebuild failed";
}
}
/* clear the tag accumulator */ /* clear the tag accumulator */
memset(tagaccum, 0, SLOTBITS_IN_LONGS * sizeof(long)); memset(tagaccum, 0, SLOTBITS_IN_LONGS * sizeof(long));
@ -806,25 +845,44 @@ static void mtip_handle_tfe(struct driver_data *dd)
for (bit = 0; bit < 32; bit++) { for (bit = 0; bit < 32; bit++) {
reissue = 1; reissue = 1;
tag = (group << 5) + bit; tag = (group << 5) + bit;
cmd = &port->commands[tag];
/* If the active bit is set re-issue the command */ /* If the active bit is set re-issue the command */
if (atomic_read(&port->commands[tag].active) == 0) if (atomic_read(&cmd->active) == 0)
continue; continue;
fis = (struct host_to_dev_fis *) fis = (struct host_to_dev_fis *)cmd->command;
port->commands[tag].command;
/* Should re-issue? */ /* Should re-issue? */
if (tag == MTIP_TAG_INTERNAL || if (tag == MTIP_TAG_INTERNAL ||
fis->command == ATA_CMD_SET_FEATURES) fis->command == ATA_CMD_SET_FEATURES)
reissue = 0; reissue = 0;
else {
if (fail_all_ncq_cmds ||
(fail_all_ncq_write &&
fis->command == ATA_CMD_FPDMA_WRITE)) {
dev_warn(&dd->pdev->dev,
" Fail: %s w/tag %d [%s].\n",
fis->command == ATA_CMD_FPDMA_WRITE ?
"write" : "read",
tag,
fail_reason != NULL ?
fail_reason : "unknown");
atomic_set(&cmd->active, 0);
if (cmd->comp_func) {
cmd->comp_func(port, tag,
cmd->comp_data,
-ENODATA);
}
continue;
}
}
/* /*
* First check if this command has * First check if this command has
* exceeded its retries. * exceeded its retries.
*/ */
if (reissue && if (reissue && (cmd->retries-- > 0)) {
(port->commands[tag].retries-- > 0)) {
set_bit(tag, tagaccum); set_bit(tag, tagaccum);
@ -837,13 +895,13 @@ static void mtip_handle_tfe(struct driver_data *dd)
/* Retire a command that will not be reissued */ /* Retire a command that will not be reissued */
dev_warn(&port->dd->pdev->dev, dev_warn(&port->dd->pdev->dev,
"retiring tag %d\n", tag); "retiring tag %d\n", tag);
atomic_set(&port->commands[tag].active, 0); atomic_set(&cmd->active, 0);
if (port->commands[tag].comp_func) if (cmd->comp_func)
port->commands[tag].comp_func( cmd->comp_func(
port, port,
tag, tag,
port->commands[tag].comp_data, cmd->comp_data,
PORT_IRQ_TF_ERR); PORT_IRQ_TF_ERR);
else else
dev_warn(&port->dd->pdev->dev, dev_warn(&port->dd->pdev->dev,
@ -1374,6 +1432,7 @@ static int mtip_standby_immediate(struct mtip_port *port)
{ {
int rv; int rv;
struct host_to_dev_fis fis; struct host_to_dev_fis fis;
unsigned long start;
/* Build the FIS. */ /* Build the FIS. */
memset(&fis, 0, sizeof(struct host_to_dev_fis)); memset(&fis, 0, sizeof(struct host_to_dev_fis));
@ -1381,15 +1440,150 @@ static int mtip_standby_immediate(struct mtip_port *port)
fis.opts = 1 << 7; fis.opts = 1 << 7;
fis.command = ATA_CMD_STANDBYNOW1; fis.command = ATA_CMD_STANDBYNOW1;
/* Execute the command. Use a 15-second timeout for large drives. */ start = jiffies;
rv = mtip_exec_internal_command(port, rv = mtip_exec_internal_command(port,
&fis, &fis,
5, 5,
0, 0,
0, 0,
0, 0,
GFP_KERNEL, GFP_ATOMIC,
15000); 15000);
dbg_printk(MTIP_DRV_NAME "Time taken to complete standby cmd: %d ms\n",
jiffies_to_msecs(jiffies - start));
if (rv)
dev_warn(&port->dd->pdev->dev,
"STANDBY IMMEDIATE command failed.\n");
return rv;
}
/*
* Issue a READ LOG EXT command to the device.
*
* @port pointer to the port structure.
* @page page number to fetch
* @buffer pointer to buffer
* @buffer_dma dma address corresponding to @buffer
* @sectors page length to fetch, in sectors
*
* return value
* @rv return value from mtip_exec_internal_command()
*/
static int mtip_read_log_page(struct mtip_port *port, u8 page, u16 *buffer,
dma_addr_t buffer_dma, unsigned int sectors)
{
struct host_to_dev_fis fis;
memset(&fis, 0, sizeof(struct host_to_dev_fis));
fis.type = 0x27;
fis.opts = 1 << 7;
fis.command = ATA_CMD_READ_LOG_EXT;
fis.sect_count = sectors & 0xFF;
fis.sect_cnt_ex = (sectors >> 8) & 0xFF;
fis.lba_low = page;
fis.lba_mid = 0;
fis.device = ATA_DEVICE_OBS;
memset(buffer, 0, sectors * ATA_SECT_SIZE);
return mtip_exec_internal_command(port,
&fis,
5,
buffer_dma,
sectors * ATA_SECT_SIZE,
0,
GFP_ATOMIC,
MTIP_INTERNAL_COMMAND_TIMEOUT_MS);
}
/*
* Issue a SMART READ DATA command to the device.
*
* @port pointer to the port structure.
* @buffer pointer to buffer
* @buffer_dma dma address corresponding to @buffer
*
* return value
* @rv return value from mtip_exec_internal_command()
*/
static int mtip_get_smart_data(struct mtip_port *port, u8 *buffer,
dma_addr_t buffer_dma)
{
struct host_to_dev_fis fis;
memset(&fis, 0, sizeof(struct host_to_dev_fis));
fis.type = 0x27;
fis.opts = 1 << 7;
fis.command = ATA_CMD_SMART;
fis.features = 0xD0;
fis.sect_count = 1;
fis.lba_mid = 0x4F;
fis.lba_hi = 0xC2;
fis.device = ATA_DEVICE_OBS;
return mtip_exec_internal_command(port,
&fis,
5,
buffer_dma,
ATA_SECT_SIZE,
0,
GFP_ATOMIC,
15000);
}
/*
* Get the value of a smart attribute
*
* @port pointer to the port structure
* @id attribute number
* @attrib pointer to return attrib information corresponding to @id
*
* return value
* -EINVAL NULL buffer passed or unsupported attribute @id.
* -EPERM Identify data not valid, SMART not supported or not enabled
*/
static int mtip_get_smart_attr(struct mtip_port *port, unsigned int id,
struct smart_attr *attrib)
{
int rv, i;
struct smart_attr *pattr;
if (!attrib)
return -EINVAL;
if (!port->identify_valid) {
dev_warn(&port->dd->pdev->dev, "IDENTIFY DATA not valid\n");
return -EPERM;
}
if (!(port->identify[82] & 0x1)) {
dev_warn(&port->dd->pdev->dev, "SMART not supported\n");
return -EPERM;
}
if (!(port->identify[85] & 0x1)) {
dev_warn(&port->dd->pdev->dev, "SMART not enabled\n");
return -EPERM;
}
memset(port->smart_buf, 0, ATA_SECT_SIZE);
rv = mtip_get_smart_data(port, port->smart_buf, port->smart_buf_dma);
if (rv) {
dev_warn(&port->dd->pdev->dev, "Failed to ge SMART data\n");
return rv;
}
pattr = (struct smart_attr *)(port->smart_buf + 2);
for (i = 0; i < 29; i++, pattr++)
if (pattr->attr_id == id) {
memcpy(attrib, pattr, sizeof(struct smart_attr));
break;
}
if (i == 29) {
dev_warn(&port->dd->pdev->dev,
"Query for invalid SMART attribute ID\n");
rv = -EINVAL;
}
return rv; return rv;
} }
@ -2272,7 +2466,7 @@ static ssize_t mtip_hw_show_registers(struct device *dev,
int size = 0; int size = 0;
int n; int n;
size += sprintf(&buf[size], "%s:\ns_active:\n", __func__); size += sprintf(&buf[size], "S ACTive:\n");
for (n = 0; n < dd->slot_groups; n++) for (n = 0; n < dd->slot_groups; n++)
size += sprintf(&buf[size], "0x%08x\n", size += sprintf(&buf[size], "0x%08x\n",
@ -2296,20 +2490,39 @@ static ssize_t mtip_hw_show_registers(struct device *dev,
group_allocated); group_allocated);
} }
size += sprintf(&buf[size], "completed:\n"); size += sprintf(&buf[size], "Completed:\n");
for (n = 0; n < dd->slot_groups; n++) for (n = 0; n < dd->slot_groups; n++)
size += sprintf(&buf[size], "0x%08x\n", size += sprintf(&buf[size], "0x%08x\n",
readl(dd->port->completed[n])); readl(dd->port->completed[n]));
size += sprintf(&buf[size], "PORT_IRQ_STAT 0x%08x\n", size += sprintf(&buf[size], "PORT IRQ STAT : 0x%08x\n",
readl(dd->port->mmio + PORT_IRQ_STAT)); readl(dd->port->mmio + PORT_IRQ_STAT));
size += sprintf(&buf[size], "HOST_IRQ_STAT 0x%08x\n", size += sprintf(&buf[size], "HOST IRQ STAT : 0x%08x\n",
readl(dd->mmio + HOST_IRQ_STAT)); readl(dd->mmio + HOST_IRQ_STAT));
return size; return size;
} }
static ssize_t mtip_hw_show_status(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct driver_data *dd = dev_to_disk(dev)->private_data;
int size = 0;
if (test_bit(MTIP_DD_FLAG_OVER_TEMP_BIT, &dd->dd_flag))
size += sprintf(buf, "%s", "thermal_shutdown\n");
else if (test_bit(MTIP_DD_FLAG_WRITE_PROTECT_BIT, &dd->dd_flag))
size += sprintf(buf, "%s", "write_protect\n");
else
size += sprintf(buf, "%s", "online\n");
return size;
}
static DEVICE_ATTR(registers, S_IRUGO, mtip_hw_show_registers, NULL); static DEVICE_ATTR(registers, S_IRUGO, mtip_hw_show_registers, NULL);
static DEVICE_ATTR(status, S_IRUGO, mtip_hw_show_status, NULL);
/* /*
* Create the sysfs related attributes. * Create the sysfs related attributes.
@ -2328,7 +2541,10 @@ static int mtip_hw_sysfs_init(struct driver_data *dd, struct kobject *kobj)
if (sysfs_create_file(kobj, &dev_attr_registers.attr)) if (sysfs_create_file(kobj, &dev_attr_registers.attr))
dev_warn(&dd->pdev->dev, dev_warn(&dd->pdev->dev,
"Error creating registers sysfs entry\n"); "Error creating 'registers' sysfs entry\n");
if (sysfs_create_file(kobj, &dev_attr_status.attr))
dev_warn(&dd->pdev->dev,
"Error creating 'status' sysfs entry\n");
return 0; return 0;
} }
@ -2348,6 +2564,7 @@ static int mtip_hw_sysfs_exit(struct driver_data *dd, struct kobject *kobj)
return -EINVAL; return -EINVAL;
sysfs_remove_file(kobj, &dev_attr_registers.attr); sysfs_remove_file(kobj, &dev_attr_registers.attr);
sysfs_remove_file(kobj, &dev_attr_status.attr);
return 0; return 0;
} }
@ -2566,6 +2783,8 @@ static int mtip_hw_init(struct driver_data *dd)
int rv; int rv;
unsigned int num_command_slots; unsigned int num_command_slots;
unsigned long timeout, timetaken; unsigned long timeout, timetaken;
unsigned char *buf;
struct smart_attr attr242;
dd->mmio = pcim_iomap_table(dd->pdev)[MTIP_ABAR]; dd->mmio = pcim_iomap_table(dd->pdev)[MTIP_ABAR];
@ -2600,7 +2819,7 @@ static int mtip_hw_init(struct driver_data *dd)
/* Allocate memory for the command list. */ /* Allocate memory for the command list. */
dd->port->command_list = dd->port->command_list =
dmam_alloc_coherent(&dd->pdev->dev, dmam_alloc_coherent(&dd->pdev->dev,
HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 2), HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 4),
&dd->port->command_list_dma, &dd->port->command_list_dma,
GFP_KERNEL); GFP_KERNEL);
if (!dd->port->command_list) { if (!dd->port->command_list) {
@ -2613,7 +2832,7 @@ static int mtip_hw_init(struct driver_data *dd)
/* Clear the memory we have allocated. */ /* Clear the memory we have allocated. */
memset(dd->port->command_list, memset(dd->port->command_list,
0, 0,
HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 2)); HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 4));
/* Setup the addresse of the RX FIS. */ /* Setup the addresse of the RX FIS. */
dd->port->rxfis = dd->port->command_list + HW_CMD_SLOT_SZ; dd->port->rxfis = dd->port->command_list + HW_CMD_SLOT_SZ;
@ -2629,10 +2848,19 @@ static int mtip_hw_init(struct driver_data *dd)
dd->port->identify_dma = dd->port->command_tbl_dma + dd->port->identify_dma = dd->port->command_tbl_dma +
HW_CMD_TBL_AR_SZ; HW_CMD_TBL_AR_SZ;
/* Setup the address of the sector buffer. */ /* Setup the address of the sector buffer - for some non-ncq cmds */
dd->port->sector_buffer = (void *) dd->port->identify + ATA_SECT_SIZE; dd->port->sector_buffer = (void *) dd->port->identify + ATA_SECT_SIZE;
dd->port->sector_buffer_dma = dd->port->identify_dma + ATA_SECT_SIZE; dd->port->sector_buffer_dma = dd->port->identify_dma + ATA_SECT_SIZE;
/* Setup the address of the log buf - for read log command */
dd->port->log_buf = (void *)dd->port->sector_buffer + ATA_SECT_SIZE;
dd->port->log_buf_dma = dd->port->sector_buffer_dma + ATA_SECT_SIZE;
/* Setup the address of the smart buf - for smart read data command */
dd->port->smart_buf = (void *)dd->port->log_buf + ATA_SECT_SIZE;
dd->port->smart_buf_dma = dd->port->log_buf_dma + ATA_SECT_SIZE;
/* Point the command headers at the command tables. */ /* Point the command headers at the command tables. */
for (i = 0; i < num_command_slots; i++) { for (i = 0; i < num_command_slots; i++) {
dd->port->commands[i].command_header = dd->port->commands[i].command_header =
@ -2759,6 +2987,43 @@ static int mtip_hw_init(struct driver_data *dd)
return MTIP_FTL_REBUILD_MAGIC; return MTIP_FTL_REBUILD_MAGIC;
} }
mtip_dump_identify(dd->port); mtip_dump_identify(dd->port);
/* check write protect, over temp and rebuild statuses */
rv = mtip_read_log_page(dd->port, ATA_LOG_SATA_NCQ,
dd->port->log_buf,
dd->port->log_buf_dma, 1);
if (rv) {
dev_warn(&dd->pdev->dev,
"Error in READ LOG EXT (10h) command\n");
/* non-critical error, don't fail the load */
} else {
buf = (unsigned char *)dd->port->log_buf;
if (buf[259] & 0x1) {
dev_info(&dd->pdev->dev,
"Write protect bit is set.\n");
set_bit(MTIP_DD_FLAG_WRITE_PROTECT_BIT, &dd->dd_flag);
}
if (buf[288] == 0xF7) {
dev_info(&dd->pdev->dev,
"Exceeded Tmax, drive in thermal shutdown.\n");
set_bit(MTIP_DD_FLAG_OVER_TEMP_BIT, &dd->dd_flag);
}
if (buf[288] == 0xBF) {
dev_info(&dd->pdev->dev,
"Drive indicates rebuild has failed.\n");
/* TODO */
}
}
/* get write protect progess */
memset(&attr242, 0, sizeof(struct smart_attr));
if (mtip_get_smart_attr(dd->port, 242, &attr242))
dev_warn(&dd->pdev->dev,
"Unable to check write protect progress\n");
else
dev_info(&dd->pdev->dev,
"Write protect progress: %d%% (%d blocks)\n",
attr242.cur, attr242.data);
return rv; return rv;
out3: out3:
@ -2776,7 +3041,7 @@ out2:
/* Free the command/command header memory. */ /* Free the command/command header memory. */
dmam_free_coherent(&dd->pdev->dev, dmam_free_coherent(&dd->pdev->dev,
HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 2), HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 4),
dd->port->command_list, dd->port->command_list,
dd->port->command_list_dma); dd->port->command_list_dma);
out1: out1:
@ -2825,7 +3090,7 @@ static int mtip_hw_exit(struct driver_data *dd)
/* Free the command/command header memory. */ /* Free the command/command header memory. */
dmam_free_coherent(&dd->pdev->dev, dmam_free_coherent(&dd->pdev->dev,
HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 2), HW_PORT_PRIV_DMA_SZ + (ATA_SECT_SIZE * 4),
dd->port->command_list, dd->port->command_list,
dd->port->command_list_dma); dd->port->command_list_dma);
/* Free the memory allocated for the for structure. */ /* Free the memory allocated for the for structure. */
@ -3378,7 +3643,7 @@ static int mtip_block_remove(struct driver_data *dd)
kthread_stop(dd->mtip_svc_handler); kthread_stop(dd->mtip_svc_handler);
} }
/* Clean up the sysfs attributes managed by the protocol layer. */ /* Clean up the sysfs attributes, if created */
if (test_bit(MTIP_DD_FLAG_INIT_DONE_BIT, &dd->dd_flag)) { if (test_bit(MTIP_DD_FLAG_INIT_DONE_BIT, &dd->dd_flag)) {
kobj = kobject_get(&disk_to_dev(dd->disk)->kobj); kobj = kobject_get(&disk_to_dev(dd->disk)->kobj);
if (kobj) { if (kobj) {

View File

@ -127,6 +127,19 @@
#define MTIP_DD_FLAG_CLEANUP_BIT 3 #define MTIP_DD_FLAG_CLEANUP_BIT 3
#define MTIP_DD_FLAG_INIT_DONE_BIT 4 #define MTIP_DD_FLAG_INIT_DONE_BIT 4
#define MTIP_DD_FLAG_WRITE_PROTECT_BIT 5
#define MTIP_DD_FLAG_OVER_TEMP_BIT 6
#define MTIP_DD_FLAG_REBUILD_FAILED_BIT 7
__packed struct smart_attr{
u8 attr_id;
u16 flags;
u8 cur;
u8 worst;
u32 data;
u8 res[3];
};
/* Register Frame Information Structure (FIS), host to device. */ /* Register Frame Information Structure (FIS), host to device. */
struct host_to_dev_fis { struct host_to_dev_fis {
/* /*
@ -351,6 +364,12 @@ struct mtip_port {
* when the command slot and all associated data structures * when the command slot and all associated data structures
* are no longer needed. * are no longer needed.
*/ */
u16 *log_buf;
dma_addr_t log_buf_dma;
u8 *smart_buf;
dma_addr_t smart_buf_dma;
unsigned long allocated[SLOTBITS_IN_LONGS]; unsigned long allocated[SLOTBITS_IN_LONGS];
/* /*
* used to queue commands when an internal command is in progress * used to queue commands when an internal command is in progress