Commit 8abf6039 authored by Mikhail Karpenko's avatar Mikhail Karpenko

Merge branch 'ahci_datascope'

parents a7e2dd1c d6018112
...@@ -144,7 +144,8 @@ static void remove_timer(struct elphel_ahci_priv *dpriv) ...@@ -144,7 +144,8 @@ static void remove_timer(struct elphel_ahci_priv *dpriv)
if (timer_pending(&dpriv->cmd_timer)) { if (timer_pending(&dpriv->cmd_timer)) {
ret = del_timer_sync(&dpriv->cmd_timer); ret = del_timer_sync(&dpriv->cmd_timer);
if (ret < 0) if (ret < 0)
dev_err(dpriv->dev, "can not remove timer\n"); dev_err(dpriv->dev, "can not remove command guard timer\n");
reset_flag(dpriv, IRQ_PROCESSED);
} }
} }
...@@ -176,10 +177,22 @@ static void reset_all_commands(struct elphel_ahci_priv *dpriv) ...@@ -176,10 +177,22 @@ static void reset_all_commands(struct elphel_ahci_priv *dpriv)
/** Command execution timer has expired, set flag indicating that recording should be restarted */ /** Command execution timer has expired, set flag indicating that recording should be restarted */
static void process_timeout(unsigned long data) static void process_timeout(unsigned long data)
{ {
int report = 0;
unsigned long irq_flags;
struct elphel_ahci_priv *dpriv = (struct elphel_ahci_priv *)data; struct elphel_ahci_priv *dpriv = (struct elphel_ahci_priv *)data;
dev_err(dpriv->dev, "AHCI error: command execution timeout, LBA start = 0x%llx\n", dpriv->lba_ptr.lba_write); /* we are racing with interrupt here, IRQ handler sets IRQ_PROCESSED flag to prevent timer from
set_flag(dpriv, START_EH); * triggering error handler before this timer is removed
*/
spin_lock_irqsave(&dpriv->flags_lock, irq_flags);
if (!(dpriv->flags & IRQ_PROCESSED)) {
dpriv->flags |= START_EH;
report = 1;
}
spin_unlock_irqrestore(&dpriv->flags_lock, irq_flags);
if (report) {
set_dscope_tstamp(dpriv, TSTMP_CMD_SYS);
}
} }
/** Calculate the difference between two time stamps and return it in microseconds */ /** Calculate the difference between two time stamps and return it in microseconds */
...@@ -229,13 +242,21 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance) ...@@ -229,13 +242,21 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)
/* handle interrupt from internal command */ /* handle interrupt from internal command */
host_irq_stat = readl(hpriv->mmio + HOST_IRQ_STAT); host_irq_stat = readl(hpriv->mmio + HOST_IRQ_STAT);
if (!host_irq_stat) if (!host_irq_stat) {
dev_err(dpriv->dev, "Spurious IRQ received, host_irq_stat = 0x%x\n", host_irq_stat);
return IRQ_NONE; return IRQ_NONE;
dpriv->flags &= ~IRQ_SIMPLE; }
reset_flag(dpriv, IRQ_SIMPLE);
irq_stat = readl(port_mmio + PORT_IRQ_STAT); irq_stat = readl(port_mmio + PORT_IRQ_STAT);
serror = readl(port_mmio + PORT_SCR_ERR); serror = readl(port_mmio + PORT_SCR_ERR);
dev_dbg(host->dev, "irq_stat = 0x%x, host irq_stat = 0x%x, SErr = 0x%x\n", irq_stat, host_irq_stat, serror); /* debug feature, save current state for analysis */
dpriv->datascope.reg_stat[REG_HOST_IS] = host_irq_stat;
dpriv->datascope.reg_stat[REG_PxIS] = irq_stat;
dpriv->datascope.reg_stat[REG_PxSERR] = serror;
dpriv->datascope.reg_stat[REG_PxIE] = readl(port_mmio + PORT_IRQ_MASK);
dpriv->datascope.reg_stat[IRQ_COUNTER] = dpriv->datascope.reg_stat[IRQ_COUNTER] + 1;
if (unlikely(irq_stat & PORT_IRQ_ERROR)) { if (unlikely(irq_stat & PORT_IRQ_ERROR)) {
dpriv->eh.irq_stat = irq_stat; dpriv->eh.irq_stat = irq_stat;
...@@ -246,21 +267,27 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance) ...@@ -246,21 +267,27 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)
writel(irq_stat, port_mmio + PORT_IRQ_STAT); writel(irq_stat, port_mmio + PORT_IRQ_STAT);
writel(host_irq_stat, hpriv->mmio + HOST_IRQ_STAT); writel(host_irq_stat, hpriv->mmio + HOST_IRQ_STAT);
handled = IRQ_HANDLED; handled = IRQ_HANDLED;
set_dscope_tstamp(dpriv, TSTMP_IRQ_MARK_1);
/* check if this command timed out and charged error handler, do not schedule tasklet in this case as /*
* check if this command timed out and charged error handler, do not schedule tasklet in this case as
* the processing will restart from error handler * the processing will restart from error handler
*/ */
spin_lock_irqsave(&dpriv->flags_lock, irq_flags);
if (dpriv->flags & (START_EH | WAIT_EH)) { if (dpriv->flags & (START_EH | WAIT_EH)) {
// tell to EH that the command will be processed in normal way // tell to EH that the command will be processed in normal way
set_flag(dpriv, DELAYED_IRQ_RCVD); set_dscope_tstamp(dpriv, TSTMP_IRQ_MARK_1);
dpriv->flags |= DELAYED_IRQ_RCVD;
wake_up_interruptible(&dpriv->eh.wait); wake_up_interruptible(&dpriv->eh.wait);
} else { } else {
dpriv->flags |= IRQ_PROCESSED;
tasklet_schedule(&dpriv->bh); tasklet_schedule(&dpriv->bh);
} }
spin_unlock_irqrestore(&dpriv->flags_lock, irq_flags);
} else { } else {
set_dscope_tstamp(dpriv, TSTMP_IRQ_SYS); set_dscope_tstamp(dpriv, TSTMP_IRQ_SYS);
dpriv->datascope.reg_stat[IRQ_COUNTER_SYS] = dpriv->datascope.reg_stat[IRQ_COUNTER_SYS] + 1;
/* pass handling to AHCI level and then decide if the resource should be freed */ /* pass handling to AHCI level and then decide if the resource should be freed */
handled = ahci_single_irq_intr(irq, dev_instance); handled = ahci_single_irq_intr(irq, dev_instance);
...@@ -275,12 +302,12 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance) ...@@ -275,12 +302,12 @@ static irqreturn_t elphel_irq_handler(int irq, void * dev_instance)
return handled; return handled;
} }
/** Command queue processing tasklet */ /** Command queue processing tasklet */
void process_queue(unsigned long data) void process_queue(unsigned long data)
{ {
int i; int i;
size_t total_sz = 0; size_t total_sz = 0;
unsigned long irq_flags;
unsigned long time_usec; unsigned long time_usec;
sec_usec_t end_time = {0}; sec_usec_t end_time = {0};
struct elphel_ahci_priv *dpriv = (struct elphel_ahci_priv *)data; struct elphel_ahci_priv *dpriv = (struct elphel_ahci_priv *)data;
...@@ -294,7 +321,7 @@ void process_queue(unsigned long data) ...@@ -294,7 +321,7 @@ void process_queue(unsigned long data)
add_sample(total_sz / time_usec, &dpriv->stat); add_sample(total_sz / time_usec, &dpriv->stat);
} }
// check if we can proceed - this was debug feature to fully stop cmd processing // // check if we can proceed - this was debug feature to fully stop cmd processing
// if (dpriv->flags & (START_EH | WAIT_EH)) // if (dpriv->flags & (START_EH | WAIT_EH))
// return; // return;
...@@ -308,9 +335,6 @@ void process_queue(unsigned long data) ...@@ -308,9 +335,6 @@ void process_queue(unsigned long data)
finish_rec(dpriv); finish_rec(dpriv);
} else { } else {
/* all commands have been processed */ /* all commands have been processed */
// spin_lock_irqsave(&dpriv->flags_lock, irq_flags);
// dpriv->flags &= ~DISK_BUSY;
// spin_unlock_irqrestore(&dpriv->flags_lock, irq_flags);
reset_flag(dpriv, DISK_BUSY); reset_flag(dpriv, DISK_BUSY);
} }
} }
...@@ -1234,7 +1258,7 @@ static size_t get_prev_slot(const struct elphel_ahci_priv *dpriv) ...@@ -1234,7 +1258,7 @@ static size_t get_prev_slot(const struct elphel_ahci_priv *dpriv)
return slot; return slot;
} }
/** Handle delayed interrupt or ather errors */ /** Handle delayed interrupt or other errors */
void error_handler(struct elphel_ahci_priv *dpriv) void error_handler(struct elphel_ahci_priv *dpriv)
{ {
long rc; long rc;
...@@ -1253,8 +1277,9 @@ void error_handler(struct elphel_ahci_priv *dpriv) ...@@ -1253,8 +1277,9 @@ void error_handler(struct elphel_ahci_priv *dpriv)
} }
rc = wait_event_interruptible_timeout(dpriv->eh.wait, dpriv->flags & DELAYED_IRQ_RCVD, deadline); rc = wait_event_interruptible_timeout(dpriv->eh.wait, dpriv->flags & DELAYED_IRQ_RCVD, deadline);
dev_warn(dpriv->dev, "wait_event_interruptible_timeout returned %ld\n", rc); dev_dbg(dpriv->dev, "wait_event_interruptible_timeout returned %ld\n", rc);
if (!rc) { if (!rc) {
reset_flag(dpriv, IRQ_SIMPLE | DISK_BUSY | PROC_CMD | START_EH);
/* interrupt has not been received after more than (DEFAULT_CMD_TIMEOUT + WAIT_IRQ_TIMEOUT) ms, /* interrupt has not been received after more than (DEFAULT_CMD_TIMEOUT + WAIT_IRQ_TIMEOUT) ms,
* try to recover the link using WAIT_IRQ_TIMEOUT value for next deadline * try to recover the link using WAIT_IRQ_TIMEOUT value for next deadline
*/ */
...@@ -1270,9 +1295,7 @@ void error_handler(struct elphel_ahci_priv *dpriv) ...@@ -1270,9 +1295,7 @@ void error_handler(struct elphel_ahci_priv *dpriv)
dpriv->max_data_sz = 0; dpriv->max_data_sz = 0;
dpriv->curr_data_chunk = 0; dpriv->curr_data_chunk = 0;
dpriv->curr_data_offset = 0; dpriv->curr_data_offset = 0;
reset_flag(dpriv, IRQ_SIMPLE | DISK_BUSY | PROC_CMD | START_EH);
dev_dbg(dpriv->dev, "repeat command from slot (head_ptr): %u\n", dpriv->head_ptr); dev_dbg(dpriv->dev, "repeat command from slot (head_ptr): %u\n", dpriv->head_ptr);
// process_cmd(dpriv);
} else { } else {
// late interrupt received or the queue was woken up, proceed to normal operation // late interrupt received or the queue was woken up, proceed to normal operation
if (rc < 0) { if (rc < 0) {
...@@ -1282,7 +1305,7 @@ void error_handler(struct elphel_ahci_priv *dpriv) ...@@ -1282,7 +1305,7 @@ void error_handler(struct elphel_ahci_priv *dpriv)
} }
dpriv->stat.last_irq_delay = jiffies_to_msecs(delay) + DEFAULT_CMD_TIMEOUT; dpriv->stat.last_irq_delay = jiffies_to_msecs(delay) + DEFAULT_CMD_TIMEOUT;
dpriv->stat.stat_ready = 1; dpriv->stat.stat_ready = 1;
dev_warn(dpriv->dev, "late interrupt received, delay is %lu ms\n", dpriv->stat.last_irq_delay); dev_dbg(dpriv->dev, "late interrupt received, delay is %lu ms\n", dpriv->stat.last_irq_delay);
reset_flag(dpriv, DELAYED_IRQ_RCVD | START_EH); reset_flag(dpriv, DELAYED_IRQ_RCVD | START_EH);
} }
reset_flag(dpriv, WAIT_EH); reset_flag(dpriv, WAIT_EH);
...@@ -1295,6 +1318,7 @@ void error_handler(struct elphel_ahci_priv *dpriv) ...@@ -1295,6 +1318,7 @@ void error_handler(struct elphel_ahci_priv *dpriv)
} }
} }
/** This function is called during write test only */
static void invalidate_cache(struct fvec *vect) static void invalidate_cache(struct fvec *vect)
{ {
__cpuc_flush_dcache_area(vect->iov_base, vect->iov_len); __cpuc_flush_dcache_area(vect->iov_base, vect->iov_len);
...@@ -1546,6 +1570,7 @@ static void elphel_cmd_issue(struct ata_port *ap,///< device port for which the ...@@ -1546,6 +1570,7 @@ static void elphel_cmd_issue(struct ata_port *ap,///< device port for which the
dma_sync_single_for_device(ap->dev, pp->cmd_tbl_dma, AHCI_CMD_TBL_AR_SZ, DMA_TO_DEVICE); dma_sync_single_for_device(ap->dev, pp->cmd_tbl_dma, AHCI_CMD_TBL_AR_SZ, DMA_TO_DEVICE);
set_dscope_tstamp(dpriv, TSTMP_CMD_DRV); set_dscope_tstamp(dpriv, TSTMP_CMD_DRV);
dpriv->datascope.reg_stat[CMD_SENT] = dpriv->datascope.reg_stat[CMD_SENT] + 1;
/* issue command */ /* issue command */
writel(0x11, port_mmio + PORT_CMD); writel(0x11, port_mmio + PORT_CMD);
...@@ -1701,7 +1726,7 @@ static ssize_t io_error_read(struct device *dev, struct device_attribute *attr, ...@@ -1701,7 +1726,7 @@ static ssize_t io_error_read(struct device *dev, struct device_attribute *attr,
return sprintf(buff, "%u\n", dpriv->io_error_flag); return sprintf(buff, "%u\n", dpriv->io_error_flag);
} }
/** Reset statistics availability flag. This will indicatate that the current samples is processed */ /** Reset statistics availability flag. This will indicate that the current sample is processed */
static ssize_t stat_delay_write(struct device *dev, struct device_attribute *attr, const char *buff, size_t buff_sz) static ssize_t stat_delay_write(struct device *dev, struct device_attribute *attr, const char *buff, size_t buff_sz)
{ {
unsigned long data; unsigned long data;
...@@ -1729,9 +1754,27 @@ static ssize_t stat_delay_read(struct device *dev, struct device_attribute *attr ...@@ -1729,9 +1754,27 @@ static ssize_t stat_delay_read(struct device *dev, struct device_attribute *attr
return scnprintf(buff, 20, "%u\n", data); return scnprintf(buff, 20, "%u\n", data);
} }
static ssize_t reg_status_read(struct device *dev, struct device_attribute *attr, char *buff)
{
int i;
ssize_t cntr;
unsigned int data;
struct elphel_ahci_priv *dpriv = dev_get_dpriv(dev);
for (i = 0, cntr = 0; i < REG_NUM; i++) {
if ((i % 8) == 0 && i != 0)
cntr += scnprintf(&buff[cntr], PAGE_SIZE - cntr - 1, "\n");
cntr += scnprintf(&buff[cntr], PAGE_SIZE - cntr - 1, "0x%08x ", dpriv->datascope.reg_stat[i]);
}
cntr += scnprintf(&buff[cntr], PAGE_SIZE - cntr - 1, "\n");
return cntr;
}
static DEVICE_ATTR(load_module, S_IWUSR | S_IWGRP, NULL, set_load_flag); static DEVICE_ATTR(load_module, S_IWUSR | S_IWGRP, NULL, set_load_flag);
static DEVICE_ATTR(io_error, S_IRUSR | S_IRGRP | S_IROTH, io_error_read, NULL); static DEVICE_ATTR(io_error, S_IRUSR | S_IRGRP | S_IROTH, io_error_read, NULL);
static DEVICE_ATTR(stat_irq_delay, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP, stat_delay_read, stat_delay_write); static DEVICE_ATTR(stat_irq_delay, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP, stat_delay_read, stat_delay_write);
static DEVICE_ATTR(reg_status, S_IRUSR | S_IRGRP | S_IROTH, reg_status_read, NULL);
static DEVICE_ATTR(SYSFS_AHCI_FNAME_WRITE, S_IWUSR | S_IWGRP, NULL, rawdev_write); static DEVICE_ATTR(SYSFS_AHCI_FNAME_WRITE, S_IWUSR | S_IWGRP, NULL, rawdev_write);
static DEVICE_ATTR(SYSFS_AHCI_FNAME_START, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP, lba_start_read, lba_start_write); static DEVICE_ATTR(SYSFS_AHCI_FNAME_START, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP, lba_start_read, lba_start_write);
static DEVICE_ATTR(SYSFS_AHCI_FNAME_END, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP, lba_end_read, lba_end_write); static DEVICE_ATTR(SYSFS_AHCI_FNAME_END, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP, lba_end_read, lba_end_write);
...@@ -1742,6 +1785,7 @@ static struct attribute *root_dev_attrs[] = { ...@@ -1742,6 +1785,7 @@ static struct attribute *root_dev_attrs[] = {
&dev_attr_load_module.attr, &dev_attr_load_module.attr,
&dev_attr_io_error.attr, &dev_attr_io_error.attr,
&dev_attr_stat_irq_delay.attr, &dev_attr_stat_irq_delay.attr,
&dev_attr_reg_status.attr,
&dev_attr_SYSFS_AHCI_FNAME_WRITE.attr, &dev_attr_SYSFS_AHCI_FNAME_WRITE.attr,
&dev_attr_SYSFS_AHCI_FNAME_START.attr, &dev_attr_SYSFS_AHCI_FNAME_START.attr,
&dev_attr_SYSFS_AHCI_FNAME_END.attr, &dev_attr_SYSFS_AHCI_FNAME_END.attr,
...@@ -1905,24 +1949,13 @@ static void set_dscope_tstamp(struct elphel_ahci_priv *dpriv, unsigned int cmd) ...@@ -1905,24 +1949,13 @@ static void set_dscope_tstamp(struct elphel_ahci_priv *dpriv, unsigned int cmd)
void __iomem *port_mmio; void __iomem *port_mmio;
uint32_t data = 0; uint32_t data = 0;
// if (!dpriv->datascope.enable)
// return;
host = dev_get_drvdata(dpriv->dev); host = dev_get_drvdata(dpriv->dev);
hpriv = host->private_data; hpriv = host->private_data;
ap = host->ports[DEFAULT_PORT_NUM]; ap = host->ports[DEFAULT_PORT_NUM];
port_mmio = ahci_port_base(ap); port_mmio = ahci_port_base(ap);
// one bit command flag (TSTMP_*) // three bits command flag (TSTMP_*)
data |= (cmd & 0x7); data |= (cmd & 0x7);
// command counter
// data |= ((dpriv->datascope.cmd_cntr & 0x3) << 1);
// if (cmd == TSTMP_IRQ_RCVD) {
// dpriv->datascope.cmd_cntr++;
// if (dpriv->datascope.cmd_cntr > 3)
// dpriv->datascope.cmd_cntr = 0;
// }
iowrite32(data, port_mmio + PORT_TIMESTAMP_ADDR); iowrite32(data, port_mmio + PORT_TIMESTAMP_ADDR);
} }
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#define START_EH (1 << 6) ///< Start error handling procedure #define START_EH (1 << 6) ///< Start error handling procedure
#define WAIT_EH (1 << 7) ///< Wait for error handler completion #define WAIT_EH (1 << 7) ///< Wait for error handler completion
#define DELAYED_IRQ_RCVD (1 << 8) ///< Delayed interrupt received and should be processed #define DELAYED_IRQ_RCVD (1 << 8) ///< Delayed interrupt received and should be processed
#define IRQ_PROCESSED (1 << 9) ///< Tell command guard timer that IRQ is received and EH should not be started
#define CMD_FIS_LEN 5 ///< The length of a command FIS in double words #define CMD_FIS_LEN 5 ///< The length of a command FIS in double words
#define ADDR_MASK_28_BIT ((u64)0xfffffff)///< This is used to get 28-bit address from 64-bit value #define ADDR_MASK_28_BIT ((u64)0xfffffff)///< This is used to get 28-bit address from 64-bit value
#define MAX_PRDT_LEN 0x3fffff ///< A maximum of length of 4MB may exist for PRDT entry #define MAX_PRDT_LEN 0x3fffff ///< A maximum of length of 4MB may exist for PRDT entry
...@@ -62,27 +63,39 @@ ...@@ -62,27 +63,39 @@
#define INCLUDE_REM 1 ///< Include REM buffer to total size calculation #define INCLUDE_REM 1 ///< Include REM buffer to total size calculation
#define EXCLUDE_REM 0 ///< Exclude REM buffer from total size calculation #define EXCLUDE_REM 0 ///< Exclude REM buffer from total size calculation
#define SPEED_SAMPLES_NUM 5 ///< Maximum number of samples for disk recording speed measurement #define SPEED_SAMPLES_NUM 5 ///< Maximum number of samples for disk recording speed measurement
#define DEFAULT_CMD_TIMEOUT 100 ///< Default timeout for commands, in ms #define DEFAULT_CMD_TIMEOUT 200 ///< Default timeout for commands, in ms
#define WAIT_IRQ_TIMEOUT 1000 ///< Wait delayed interrupt for this amount of time, in ms #define WAIT_IRQ_TIMEOUT 5000 ///< Wait delayed interrupt for this amount of time, in ms
#define TSTMP_CMD_SYS 1 ///< command issued by system #define TSTMP_CMD_SYS 1 ///< command issued by system
#define TSTMP_CMD_DRV 2 ///< command issued by driver #define TSTMP_CMD_DRV 2 ///< command issued by driver
#define TSTMP_IRQ_SYS 3 ///< irq processed by system #define TSTMP_IRQ_SYS 3 ///< irq processed by system
#define TSTMP_IRQ_DRV 4 ///< irq processed by driver #define TSTMP_IRQ_DRV 4 ///< irq processed by driver
#define TSTMP_IRQ_MARK_1 5 ///< additional time stamp mark
enum { enum {
PORT_VS = 0x70, ///< vendor specific port address PORT_VS = 0x70, ///< vendor specific port address
PORT_TIMESTAMP_ADDR = 0x78 ///< datascope timestamp register PORT_TIMESTAMP_ADDR = 0x78 ///< datascope timestamp register
}; };
enum {
REG_NUM = 128, ///< total number of DWs in status buffer
REG_HOST_IS = 0, ///< value of host IS register in data buffer
REG_PxIS, ///< value of PxIS in data buffer
REG_PxIE, ///< value of PxIE in data buffer
REG_PxSERR, ///< value of PxSERR in data buffer
IRQ_COUNTER, ///< interrupts counter for internal commands
IRQ_COUNTER_SYS, ///< interrupts counter for system commands
CMD_SENT ///< number of commands sent
};
struct datascope { struct datascope {
void __iomem *timestamp_reg; ///< register in vendor specific address range (PxVS) where timestamp can be written void __iomem *timestamp_reg; ///< register in vendor specific address range (PxVS) where timestamp can be written
unsigned int cmd_cntr; ///< command counter, its value can be found in timestamp (2 bits only) unsigned int cmd_cntr; ///< command counter, its value can be found in timestamp (2 bits only)
unsigned int enable; ///< enable/disable timestamps unsigned int enable; ///< enable/disable timestamps
uint32_t reg_stat[REG_NUM]; ///< buffer for registers' status, filled in in IRQ
}; };
/** Data for error handler */ /** Data for error handler */
struct error_handler { struct error_handler {
uint32_t s_error; uint32_t s_error; ///< the content of PxSERR register
uint32_t irq_stat; uint32_t irq_stat; ///< the content of PxIS register
wait_queue_head_t wait; ///< wait queue for delayed interrupts wait_queue_head_t wait; ///< wait queue for delayed interrupts
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment