* [PATCH] pm8001: enhance firmware event log retrieval @ 2012-01-18 15:48 Mark Salyzyn 2012-01-19 7:37 ` Jack Wang 2012-02-18 15:20 ` James Bottomley 0 siblings, 2 replies; 10+ messages in thread From: Mark Salyzyn @ 2012-01-18 15:48 UTC (permalink / raw) To: linux-scsi Cc: Mark Salyzyn, Jack Wang, crystal_yu, lindar_liu, James Bottomley The pm8001 driver event log currently only reports the current firmware event log index and the first entry (if log has not wrapped). The enclosed patch enhances the event log retrieved to sequentially return all un-retrieved entries until hitting the last, from then on reporting the last entry. The format has been changed in a parseable fashion reporting some of the interpreted fields; the entry sequence number can be used to determine the boundary condition when the last entry is retrieved. Formerly, when the logging level is changed at runtime, it does not transmit the updated level to the firmware. We also added a set of insmod parameters so that the logging level may be set at initialization time. As this disrupts backward compatibility, we look forward to community comments. Also, once an earlier log entry is retrieved, it is the expectation of the logging/forensic program(?) to record them as they can not be retrieved again. Please keep in mind that the first event log entry as previously reported by the existing code becomes somewhat moot if new entries form. Signed-off-by: mark_salyzyn@xyratex.com Cc: jack_wang@usish.com Cc: JBottomley@parallels.com Cc: crystal_yu@usish.com Cc: john_gong@usish.com Cc: lindar_liu <lindar_liu@usish.com> diff --git a/drivers/scsi/pm8001/pm8001_ctl.c b/drivers/scsi/pm8001/pm8001_ctl.c index 45bc197..3fe3d93 100644 --- a/drivers/scsi/pm8001/pm8001_ctl.c +++ b/drivers/scsi/pm8001/pm8001_ctl.c @@ -228,46 +228,140 @@ static ssize_t pm8001_ctl_logging_level_store(struct device *cdev, return -EINVAL; pm8001_ha->logging_level = val; + pm8001_update_main_config_table(pm8001_ha); return strlen(buf); } static DEVICE_ATTR(logging_level, S_IRUGO | S_IWUSR, pm8001_ctl_logging_level_show, pm8001_ctl_logging_level_store); + /** - * pm8001_ctl_aap_log_show - aap1 event log + * pm8001_readlog - generic routine to read a single event log entry + * @header: a pointer to the event log base header + * @entry: a pointer to copy the latest event log into + * @consumer_index: a current local index pointing to the last oldest entry + * retrieved. Automatically incremented and wrapped. + * @return: A copied entry content and a status. Status return of zero means + * more entries available, and the consumer index is incremented and + * wrapped. A positive status return means last entry, the consumer + * index may be incremented depending of approached or at the last + * entry. Status can also return a negative error code. It is the + * responsibility of the caller to confirm the validity of the copied + * event information. + */ +int pm8001_readlog( + struct eventlog_header *header, + struct eventlog_entry *entry, + u32 *consumer_index) +{ + unsigned maximum_index; + unsigned producer_index; + + /* Validity of header */ + if ((header->offset != sizeof(*header)) || + (header->entry_size != sizeof(*entry)) || + ((header->signature != EVENTLOG_HEADER_SIGNATURE_AAP1) && + (header->signature != EVENTLOG_HEADER_SIGNATURE_IOP))) + return -EINVAL; + /* Validity of hardware indexii */ + maximum_index = header->size / sizeof(*entry); + if ((maximum_index <= header->producer_index) || + (maximum_index <= header->consumer_index)) + return -EINVAL; + producer_index = header->producer_index; + if (producer_index == 0) + producer_index = maximum_index - 1; + else + --producer_index; + /* Validity of current consumer index */ + if (((producer_index < header->consumer_index) ? + ((producer_index < *consumer_index) && + (*consumer_index < header->consumer_index)) : + ((producer_index < *consumer_index) || + (*consumer_index < header->consumer_index))) || + (*consumer_index >= maximum_index)) + *consumer_index = header->consumer_index; + *entry = ((struct eventlog_entry *)(header + 1))[*consumer_index]; + if (*consumer_index != producer_index) { + ++(*consumer_index); + if (*consumer_index >= maximum_index) + *consumer_index = 0; + /* More entries? */ + return *consumer_index == producer_index; + } + return 2; /* Already at Last Entry */ +} + +/** + * pm8001_validlog - generic routine to validate a single event log entry + * @entry: a pointer to the event log entry + * @return: zero if not valid + */ +static inline int pm8001_validlog(struct eventlog_entry *entry) +{ + return ((entry->size <= (sizeof(entry->log) / sizeof(entry->log[0]))) && + ((entry->timestamp_upper != 0) || + (entry->timestamp_lower != 0))); +} + +/** + * pm8001_ctl_log_show - event log * @cdev: pointer to embedded class device * @buf: the buffer returned + * @ind: The region index (AAP1 or IOP) * * A sysfs 'read-only' shost attribute. */ -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, - struct device_attribute *attr, char *buf) +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) { struct Scsi_Host *shost = class_to_shost(cdev); struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; + struct eventlog_entry entry; + char *str = buf; int i; -#define AAP1_MEMMAP(r, c) \ - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 \ - + (c))) - char *str = buf; - int max = 2; - for (i = 0; i < max; i++) { - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" - "0x%08x 0x%08x\n", - AAP1_MEMMAP(i, 0), - AAP1_MEMMAP(i, 4), - AAP1_MEMMAP(i, 8), - AAP1_MEMMAP(i, 12), - AAP1_MEMMAP(i, 16), - AAP1_MEMMAP(i, 20), - AAP1_MEMMAP(i, 24), - AAP1_MEMMAP(i, 28)); + memset(&entry, 0, sizeof(entry)); + do { + i = pm8001_readlog( + (struct eventlog_header *)( + pm8001_ha->memoryMap.region[ind].virt_ptr), + &entry, + (ind == AAP1) ? + &pm8001_ha->aap1_consumer : + &pm8001_ha->iop_consumer); + } while ((i == 0) && !pm8001_validlog(&entry)); + if ((i >= 0) && pm8001_validlog(&entry)) { + u32 *lp; + unsigned long long timestamp = + (((unsigned long long)entry.timestamp_upper) << 32) | + entry.timestamp_lower; + str += snprintf(str, PAGE_SIZE - (str - buf), + "%u %llu.%09llu %u", + entry.severity, + timestamp / (1000000000 / 8), + (timestamp % (1000000000 / 8)) * 8, + entry.sequence); + for (lp = entry.log, i = entry.size; i > 0; --i) + str += snprintf(str, PAGE_SIZE - (str - buf), + " 0x%08x", *(lp++)); + str += snprintf(str, PAGE_SIZE - (str - buf), "\n"); } - return str - buf; } + +/** + * pm8001_ctl_aap_log_show - aap1 event log + * @cdev: pointer to embedded class device + * @buf: the buffer returned + * + * A sysfs 'read-only' shost attribute. + */ +static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, + struct device_attribute *attr, char *buf) +{ + return pm8001_ctl_log_show(cdev, buf, AAP1); +} static DEVICE_ATTR(aap_log, S_IRUGO, pm8001_ctl_aap_log_show, NULL); /** * pm8001_ctl_aap_log_show - IOP event log @@ -279,29 +373,7 @@ static DEVICE_ATTR(aap_log, S_IRUGO, pm8001_ctl_aap_log_show, NULL); static ssize_t pm8001_ctl_iop_log_show(struct device *cdev, struct device_attribute *attr, char *buf) { - struct Scsi_Host *shost = class_to_shost(cdev); - struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); - struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; -#define IOP_MEMMAP(r, c) \ - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[IOP].virt_ptr + (r) * 32 \ - + (c))) - int i; - char *str = buf; - int max = 2; - for (i = 0; i < max; i++) { - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" - "0x%08x 0x%08x\n", - IOP_MEMMAP(i, 0), - IOP_MEMMAP(i, 4), - IOP_MEMMAP(i, 8), - IOP_MEMMAP(i, 12), - IOP_MEMMAP(i, 16), - IOP_MEMMAP(i, 20), - IOP_MEMMAP(i, 24), - IOP_MEMMAP(i, 28)); - } - - return str - buf; + return pm8001_ctl_log_show(cdev, buf, IOP); } static DEVICE_ATTR(iop_log, S_IRUGO, pm8001_ctl_iop_log_show, NULL); diff --git a/drivers/scsi/pm8001/pm8001_hwi.c b/drivers/scsi/pm8001/pm8001_hwi.c index b7b92f7..b3bd781 100644 --- a/drivers/scsi/pm8001/pm8001_hwi.c +++ b/drivers/scsi/pm8001/pm8001_hwi.c @@ -250,8 +250,8 @@ init_default_table_values(struct pm8001_hba_info *pm8001_ha) * update_main_config_table - update the main default table to the HBA. * @pm8001_ha: our hba card information */ -static void __devinit -update_main_config_table(struct pm8001_hba_info *pm8001_ha) +void +pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha) { void __iomem *address = pm8001_ha->main_cfg_tbl_addr; pm8001_mw32(address, 0x24, @@ -603,7 +603,7 @@ static int __devinit pm8001_chip_init(struct pm8001_hba_info *pm8001_ha) read_inbnd_queue_table(pm8001_ha); read_outbnd_queue_table(pm8001_ha); /* update main config table ,inbound table and outbound table */ - update_main_config_table(pm8001_ha); + pm8001_update_main_config_table(pm8001_ha); update_inbnd_queue_table(pm8001_ha, 0); update_outbnd_queue_table(pm8001_ha, 0); mpi_set_phys_g3_with_ssc(pm8001_ha, 0); diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index c21a216..c638da4 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c @@ -48,6 +48,8 @@ static const struct pm8001_chip_info pm8001_chips[] = { [chip_8001] = { 8, &pm8001_8001_dispatch,}, }; static int pm8001_id; +static int pm8001_logging_level = PM8001_FAIL_LOGGING; +static int pm8001_logging_size = PM8001_EVENT_LOG_SIZE; LIST_HEAD(hba_list); @@ -212,16 +214,19 @@ static int __devinit pm8001_alloc(struct pm8001_hba_info *pm8001_ha) pm8001_ha->tags = kzalloc(PM8001_MAX_CCB, GFP_KERNEL); if (!pm8001_ha->tags) goto err_out; + pm8001_logging_size = ((pm8001_logging_size + 31) / 32) * 32; + if (pm8001_logging_size < 64) + pm8001_logging_size = 64; /* MPI Memory region 1 for AAP Event Log for fw */ pm8001_ha->memoryMap.region[AAP1].num_elements = 1; - pm8001_ha->memoryMap.region[AAP1].element_size = PM8001_EVENT_LOG_SIZE; - pm8001_ha->memoryMap.region[AAP1].total_len = PM8001_EVENT_LOG_SIZE; + pm8001_ha->memoryMap.region[AAP1].element_size = pm8001_logging_size; + pm8001_ha->memoryMap.region[AAP1].total_len = pm8001_logging_size; pm8001_ha->memoryMap.region[AAP1].alignment = 32; /* MPI Memory region 2 for IOP Event Log for fw */ pm8001_ha->memoryMap.region[IOP].num_elements = 1; - pm8001_ha->memoryMap.region[IOP].element_size = PM8001_EVENT_LOG_SIZE; - pm8001_ha->memoryMap.region[IOP].total_len = PM8001_EVENT_LOG_SIZE; + pm8001_ha->memoryMap.region[IOP].element_size = pm8001_logging_size; + pm8001_ha->memoryMap.region[IOP].total_len = pm8001_logging_size; pm8001_ha->memoryMap.region[IOP].alignment = 32; /* MPI Memory region 3 for consumer Index of inbound queues */ @@ -242,7 +247,7 @@ static int __devinit pm8001_alloc(struct pm8001_hba_info *pm8001_ha) pm8001_ha->memoryMap.region[IB].total_len = 256 * 64; pm8001_ha->memoryMap.region[IB].alignment = 64; - /* MPI Memory region 6 inbound queues */ + /* MPI Memory region 6 outbound queues */ pm8001_ha->memoryMap.region[OB].num_elements = 256; pm8001_ha->memoryMap.region[OB].element_size = 64; pm8001_ha->memoryMap.region[OB].total_len = 256 * 64; @@ -381,7 +386,7 @@ pm8001_pci_alloc(struct pci_dev *pdev, u32 chip_id, struct Scsi_Host *shost) pm8001_ha->sas = sha; pm8001_ha->shost = shost; pm8001_ha->id = pm8001_id++; - pm8001_ha->logging_level = 0x01; + pm8001_ha->logging_level = pm8001_logging_level; sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); #ifdef PM8001_USE_TASKLET tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, @@ -614,8 +619,8 @@ intx: * @ent: pci device id * * This function is the main initialization function, when register a new - * pci driver it is invoked, all struct an hardware initilization should be done - * here, also, register interrupt + * pci driver it is invoked, all struct and hardware initialization should be + * done here, also, register interrupt */ static int __devinit pm8001_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) @@ -899,6 +904,9 @@ static void __exit pm8001_exit(void) destroy_workqueue(pm8001_wq); } +module_param(pm8001_logging_level, int, 0); +module_param_named(logging_size, pm8001_logging_size, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(logging_size, "Event logging buffer size"); module_init(pm8001_init); module_exit(pm8001_exit); diff --git a/drivers/scsi/pm8001/pm8001_sas.h b/drivers/scsi/pm8001/pm8001_sas.h index 93959fe..84d3170 100644 --- a/drivers/scsi/pm8001/pm8001_sas.h +++ b/drivers/scsi/pm8001/pm8001_sas.h @@ -336,6 +336,29 @@ struct outbound_queue_table { __le32 producer_index; u32 consumer_idx; }; +struct eventlog_header { + __le32 signature; +#define EVENTLOG_HEADER_SIGNATURE_AAP1 0x1234AAAA +#define EVENTLOG_HEADER_SIGNATURE_IOP 0x5678CCCC + __le32 offset; /* sizeof(eventlog_header) */ + u32 reserved1; + __le32 size; + u32 reserved2; + __le32 consumer_index; + __le32 producer_index; + __le32 entry_size; +}; +struct eventlog_entry { + u8 size:3; + u8 reserved1:5; + u8 reserved2[2]; + u8 reserved:4; + u8 severity:4; + __le32 timestamp_upper; + __le32 timestamp_lower; + __le32 sequence; + __le32 log[4]; +}; struct pm8001_hba_memspace { void __iomem *memvirtaddr; u64 membase; @@ -383,6 +406,9 @@ struct pm8001_hba_info { u32 logging_level; u32 fw_status; const struct firmware *fw_image; + /* Local consumer indexes in support of sysfs event log node */ + u32 aap1_consumer; + u32 iop_consumer; }; struct pm8001_work { @@ -488,6 +514,11 @@ int pm8001_mem_alloc(struct pci_dev *pdev, void **virt_addr, dma_addr_t *pphys_addr, u32 *pphys_addr_hi, u32 *pphys_addr_lo, u32 mem_size, u32 align); +void pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha); +int pm8001_readlog( + struct eventlog_header *header, + struct eventlog_entry *entry, + u32 *consumer_index); /* ctl shared API */ extern struct device_attribute *pm8001_host_attrs[]; ^ permalink raw reply related [flat|nested] 10+ messages in thread
* RE: [PATCH] pm8001: enhance firmware event log retrieval 2012-01-18 15:48 [PATCH] pm8001: enhance firmware event log retrieval Mark Salyzyn @ 2012-01-19 7:37 ` Jack Wang 2012-01-19 13:46 ` Mark Salyzyn 2012-02-18 15:20 ` James Bottomley 1 sibling, 1 reply; 10+ messages in thread From: Jack Wang @ 2012-01-19 7:37 UTC (permalink / raw) To: 'Mark Salyzyn', 'linux-scsi' Cc: 'crystal_yu', 'lindar_liu', 'James Bottomley' > The pm8001 driver event log currently only reports the current firmware event > log index and the first entry (if log has not wrapped). The enclosed patch > enhances the event log retrieved to sequentially return all un-retrieved > entries until hitting the last, from then on reporting the last entry. The > format has been changed in a parseable fashion reporting some of the interpreted > fields; the entry sequence number can be used to determine the boundary > condition when the last entry is retrieved. > > Formerly, when the logging level is changed at runtime, it does not transmit > the updated level to the firmware. We also added a set of insmod parameters > so that the logging level may be set at initialization time. > [Jack Wang] Patch is welcome. > As this disrupts backward compatibility, we look forward to community comments. > Also, once an earlier log entry is retrieved, it is the expectation of the > logging/forensic program(?) to record them as they can not be retrieved again. > Please keep in mind that the first event log entry as previously reported by > the existing code becomes somewhat moot if new entries form. > [Jack Wang] I wonder what user need this function? How do you test this function? > Signed-off-by: mark_salyzyn@xyratex.com > Cc: jack_wang@usish.com > Cc: JBottomley@parallels.com > Cc: crystal_yu@usish.com > Cc: john_gong@usish.com > Cc: lindar_liu <lindar_liu@usish.com> [Jack Wang] Acked-by: Jack Wang <jack_wang@usish.com> Thanks. > > diff --git a/drivers/scsi/pm8001/pm8001_ctl.c > b/drivers/scsi/pm8001/pm8001_ctl.c > index 45bc197..3fe3d93 100644 > --- a/drivers/scsi/pm8001/pm8001_ctl.c > +++ b/drivers/scsi/pm8001/pm8001_ctl.c > @@ -228,46 +228,140 @@ static ssize_t pm8001_ctl_logging_level_store(struct > device *cdev, > return -EINVAL; > > pm8001_ha->logging_level = val; > + pm8001_update_main_config_table(pm8001_ha); > return strlen(buf); > } > > static DEVICE_ATTR(logging_level, S_IRUGO | S_IWUSR, > pm8001_ctl_logging_level_show, pm8001_ctl_logging_level_store); > + > /** > - * pm8001_ctl_aap_log_show - aap1 event log > + * pm8001_readlog - generic routine to read a single event log entry > + * @header: a pointer to the event log base header > + * @entry: a pointer to copy the latest event log into > + * @consumer_index: a current local index pointing to the last oldest entry > + * retrieved. Automatically incremented and wrapped. > + * @return: A copied entry content and a status. Status return of zero means > + * more entries available, and the consumer index is incremented and > + * wrapped. A positive status return means last entry, the consumer > + * index may be incremented depending of approached or at the last > + * entry. Status can also return a negative error code. It is the > + * responsibility of the caller to confirm the validity of the copied > + * event information. > + */ > +int pm8001_readlog( > + struct eventlog_header *header, > + struct eventlog_entry *entry, > + u32 *consumer_index) > +{ > + unsigned maximum_index; > + unsigned producer_index; > + > + /* Validity of header */ > + if ((header->offset != sizeof(*header)) || > + (header->entry_size != sizeof(*entry)) || > + ((header->signature != EVENTLOG_HEADER_SIGNATURE_AAP1) && > + (header->signature != EVENTLOG_HEADER_SIGNATURE_IOP))) > + return -EINVAL; > + /* Validity of hardware indexii */ > + maximum_index = header->size / sizeof(*entry); > + if ((maximum_index <= header->producer_index) || > + (maximum_index <= header->consumer_index)) > + return -EINVAL; > + producer_index = header->producer_index; > + if (producer_index == 0) > + producer_index = maximum_index - 1; > + else > + --producer_index; > + /* Validity of current consumer index */ > + if (((producer_index < header->consumer_index) ? > + ((producer_index < *consumer_index) && > + (*consumer_index < header->consumer_index)) : > + ((producer_index < *consumer_index) || > + (*consumer_index < header->consumer_index))) || > + (*consumer_index >= maximum_index)) > + *consumer_index = header->consumer_index; > + *entry = ((struct eventlog_entry *)(header + 1))[*consumer_index]; > + if (*consumer_index != producer_index) { > + ++(*consumer_index); > + if (*consumer_index >= maximum_index) > + *consumer_index = 0; > + /* More entries? */ > + return *consumer_index == producer_index; > + } > + return 2; /* Already at Last Entry */ > +} > + > +/** > + * pm8001_validlog - generic routine to validate a single event log entry > + * @entry: a pointer to the event log entry > + * @return: zero if not valid > + */ > +static inline int pm8001_validlog(struct eventlog_entry *entry) > +{ > + return ((entry->size <= (sizeof(entry->log) / sizeof(entry->log[0]))) && > + ((entry->timestamp_upper != 0) || > + (entry->timestamp_lower != 0))); > +} > + > +/** > + * pm8001_ctl_log_show - event log > * @cdev: pointer to embedded class device > * @buf: the buffer returned > + * @ind: The region index (AAP1 or IOP) > * > * A sysfs 'read-only' shost attribute. > */ > -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, > - struct device_attribute *attr, char *buf) > +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) > { > struct Scsi_Host *shost = class_to_shost(cdev); > struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); > struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; > + struct eventlog_entry entry; > + char *str = buf; > int i; > -#define AAP1_MEMMAP(r, c) \ > - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 \ > - + (c))) > > - char *str = buf; > - int max = 2; > - for (i = 0; i < max; i++) { > - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" > - "0x%08x 0x%08x\n", > - AAP1_MEMMAP(i, 0), > - AAP1_MEMMAP(i, 4), > - AAP1_MEMMAP(i, 8), > - AAP1_MEMMAP(i, 12), > - AAP1_MEMMAP(i, 16), > - AAP1_MEMMAP(i, 20), > - AAP1_MEMMAP(i, 24), > - AAP1_MEMMAP(i, 28)); > + memset(&entry, 0, sizeof(entry)); > + do { > + i = pm8001_readlog( > + (struct eventlog_header *)( > + pm8001_ha->memoryMap.region[ind].virt_ptr), > + &entry, > + (ind == AAP1) ? > + &pm8001_ha->aap1_consumer : > + &pm8001_ha->iop_consumer); > + } while ((i == 0) && !pm8001_validlog(&entry)); > + if ((i >= 0) && pm8001_validlog(&entry)) { > + u32 *lp; > + unsigned long long timestamp = > + (((unsigned long long)entry.timestamp_upper) << 32) | > + entry.timestamp_lower; > + str += snprintf(str, PAGE_SIZE - (str - buf), > + "%u %llu.%09llu %u", > + entry.severity, > + timestamp / (1000000000 / 8), > + (timestamp % (1000000000 / 8)) * 8, > + entry.sequence); > + for (lp = entry.log, i = entry.size; i > 0; --i) > + str += snprintf(str, PAGE_SIZE - (str - buf), > + " 0x%08x", *(lp++)); > + str += snprintf(str, PAGE_SIZE - (str - buf), "\n"); > } > - > return str - buf; > } > + > +/** > + * pm8001_ctl_aap_log_show - aap1 event log > + * @cdev: pointer to embedded class device > + * @buf: the buffer returned > + * > + * A sysfs 'read-only' shost attribute. > + */ > +static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, > + struct device_attribute *attr, char *buf) > +{ > + return pm8001_ctl_log_show(cdev, buf, AAP1); > +} > static DEVICE_ATTR(aap_log, S_IRUGO, pm8001_ctl_aap_log_show, NULL); > /** > * pm8001_ctl_aap_log_show - IOP event log > @@ -279,29 +373,7 @@ static DEVICE_ATTR(aap_log, S_IRUGO, > pm8001_ctl_aap_log_show, NULL); > static ssize_t pm8001_ctl_iop_log_show(struct device *cdev, > struct device_attribute *attr, char *buf) > { > - struct Scsi_Host *shost = class_to_shost(cdev); > - struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); > - struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; > -#define IOP_MEMMAP(r, c) \ > - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[IOP].virt_ptr + (r) * 32 \ > - + (c))) > - int i; > - char *str = buf; > - int max = 2; > - for (i = 0; i < max; i++) { > - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" > - "0x%08x 0x%08x\n", > - IOP_MEMMAP(i, 0), > - IOP_MEMMAP(i, 4), > - IOP_MEMMAP(i, 8), > - IOP_MEMMAP(i, 12), > - IOP_MEMMAP(i, 16), > - IOP_MEMMAP(i, 20), > - IOP_MEMMAP(i, 24), > - IOP_MEMMAP(i, 28)); > - } > - > - return str - buf; > + return pm8001_ctl_log_show(cdev, buf, IOP); > } > static DEVICE_ATTR(iop_log, S_IRUGO, pm8001_ctl_iop_log_show, NULL); > > diff --git a/drivers/scsi/pm8001/pm8001_hwi.c > b/drivers/scsi/pm8001/pm8001_hwi.c > index b7b92f7..b3bd781 100644 > --- a/drivers/scsi/pm8001/pm8001_hwi.c > +++ b/drivers/scsi/pm8001/pm8001_hwi.c > @@ -250,8 +250,8 @@ init_default_table_values(struct pm8001_hba_info > *pm8001_ha) > * update_main_config_table - update the main default table to the HBA. > * @pm8001_ha: our hba card information > */ > -static void __devinit > -update_main_config_table(struct pm8001_hba_info *pm8001_ha) > +void > +pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha) > { > void __iomem *address = pm8001_ha->main_cfg_tbl_addr; > pm8001_mw32(address, 0x24, > @@ -603,7 +603,7 @@ static int __devinit pm8001_chip_init(struct > pm8001_hba_info *pm8001_ha) > read_inbnd_queue_table(pm8001_ha); > read_outbnd_queue_table(pm8001_ha); > /* update main config table ,inbound table and outbound table */ > - update_main_config_table(pm8001_ha); > + pm8001_update_main_config_table(pm8001_ha); > update_inbnd_queue_table(pm8001_ha, 0); > update_outbnd_queue_table(pm8001_ha, 0); > mpi_set_phys_g3_with_ssc(pm8001_ha, 0); > diff --git a/drivers/scsi/pm8001/pm8001_init.c > b/drivers/scsi/pm8001/pm8001_init.c > index c21a216..c638da4 100644 > --- a/drivers/scsi/pm8001/pm8001_init.c > +++ b/drivers/scsi/pm8001/pm8001_init.c > @@ -48,6 +48,8 @@ static const struct pm8001_chip_info pm8001_chips[] = { > [chip_8001] = { 8, &pm8001_8001_dispatch,}, > }; > static int pm8001_id; > +static int pm8001_logging_level = PM8001_FAIL_LOGGING; > +static int pm8001_logging_size = PM8001_EVENT_LOG_SIZE; > > LIST_HEAD(hba_list); > > @@ -212,16 +214,19 @@ static int __devinit pm8001_alloc(struct pm8001_hba_info > *pm8001_ha) > pm8001_ha->tags = kzalloc(PM8001_MAX_CCB, GFP_KERNEL); > if (!pm8001_ha->tags) > goto err_out; > + pm8001_logging_size = ((pm8001_logging_size + 31) / 32) * 32; > + if (pm8001_logging_size < 64) > + pm8001_logging_size = 64; > /* MPI Memory region 1 for AAP Event Log for fw */ > pm8001_ha->memoryMap.region[AAP1].num_elements = 1; > - pm8001_ha->memoryMap.region[AAP1].element_size = > PM8001_EVENT_LOG_SIZE; > - pm8001_ha->memoryMap.region[AAP1].total_len = PM8001_EVENT_LOG_SIZE; > + pm8001_ha->memoryMap.region[AAP1].element_size = pm8001_logging_size; > + pm8001_ha->memoryMap.region[AAP1].total_len = pm8001_logging_size; > pm8001_ha->memoryMap.region[AAP1].alignment = 32; > > /* MPI Memory region 2 for IOP Event Log for fw */ > pm8001_ha->memoryMap.region[IOP].num_elements = 1; > - pm8001_ha->memoryMap.region[IOP].element_size = PM8001_EVENT_LOG_SIZE; > - pm8001_ha->memoryMap.region[IOP].total_len = PM8001_EVENT_LOG_SIZE; > + pm8001_ha->memoryMap.region[IOP].element_size = pm8001_logging_size; > + pm8001_ha->memoryMap.region[IOP].total_len = pm8001_logging_size; > pm8001_ha->memoryMap.region[IOP].alignment = 32; > > /* MPI Memory region 3 for consumer Index of inbound queues */ > @@ -242,7 +247,7 @@ static int __devinit pm8001_alloc(struct pm8001_hba_info > *pm8001_ha) > pm8001_ha->memoryMap.region[IB].total_len = 256 * 64; > pm8001_ha->memoryMap.region[IB].alignment = 64; > > - /* MPI Memory region 6 inbound queues */ > + /* MPI Memory region 6 outbound queues */ > pm8001_ha->memoryMap.region[OB].num_elements = 256; > pm8001_ha->memoryMap.region[OB].element_size = 64; > pm8001_ha->memoryMap.region[OB].total_len = 256 * 64; > @@ -381,7 +386,7 @@ pm8001_pci_alloc(struct pci_dev *pdev, u32 chip_id, struct > Scsi_Host *shost) > pm8001_ha->sas = sha; > pm8001_ha->shost = shost; > pm8001_ha->id = pm8001_id++; > - pm8001_ha->logging_level = 0x01; > + pm8001_ha->logging_level = pm8001_logging_level; > sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); > #ifdef PM8001_USE_TASKLET > tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, > @@ -614,8 +619,8 @@ intx: > * @ent: pci device id > * > * This function is the main initialization function, when register a new > - * pci driver it is invoked, all struct an hardware initilization should be > done > - * here, also, register interrupt > + * pci driver it is invoked, all struct and hardware initialization should > be > + * done here, also, register interrupt > */ > static int __devinit pm8001_pci_probe(struct pci_dev *pdev, > const struct pci_device_id *ent) > @@ -899,6 +904,9 @@ static void __exit pm8001_exit(void) > destroy_workqueue(pm8001_wq); > } > > +module_param(pm8001_logging_level, int, 0); > +module_param_named(logging_size, pm8001_logging_size, int, > S_IRUGO|S_IWUSR); > +MODULE_PARM_DESC(logging_size, "Event logging buffer size"); > module_init(pm8001_init); > module_exit(pm8001_exit); > > diff --git a/drivers/scsi/pm8001/pm8001_sas.h > b/drivers/scsi/pm8001/pm8001_sas.h > index 93959fe..84d3170 100644 > --- a/drivers/scsi/pm8001/pm8001_sas.h > +++ b/drivers/scsi/pm8001/pm8001_sas.h > @@ -336,6 +336,29 @@ struct outbound_queue_table { > __le32 producer_index; > u32 consumer_idx; > }; > +struct eventlog_header { > + __le32 signature; > +#define EVENTLOG_HEADER_SIGNATURE_AAP1 0x1234AAAA > +#define EVENTLOG_HEADER_SIGNATURE_IOP 0x5678CCCC > + __le32 offset; /* sizeof(eventlog_header) */ > + u32 reserved1; > + __le32 size; > + u32 reserved2; > + __le32 consumer_index; > + __le32 producer_index; > + __le32 entry_size; > +}; > +struct eventlog_entry { > + u8 size:3; > + u8 reserved1:5; > + u8 reserved2[2]; > + u8 reserved:4; > + u8 severity:4; > + __le32 timestamp_upper; > + __le32 timestamp_lower; > + __le32 sequence; > + __le32 log[4]; > +}; > struct pm8001_hba_memspace { > void __iomem *memvirtaddr; > u64 membase; > @@ -383,6 +406,9 @@ struct pm8001_hba_info { > u32 logging_level; > u32 fw_status; > const struct firmware *fw_image; > + /* Local consumer indexes in support of sysfs event log node */ > + u32 aap1_consumer; > + u32 iop_consumer; > }; > > struct pm8001_work { > @@ -488,6 +514,11 @@ int pm8001_mem_alloc(struct pci_dev *pdev, void > **virt_addr, > dma_addr_t *pphys_addr, u32 *pphys_addr_hi, u32 *pphys_addr_lo, > u32 mem_size, u32 align); > > +void pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha); > +int pm8001_readlog( > + struct eventlog_header *header, > + struct eventlog_entry *entry, > + u32 *consumer_index); > > /* ctl shared API */ > extern struct device_attribute *pm8001_host_attrs[]; > > ______________________________________________________________________ > This email may contain privileged or confidential information, which should > only be used for the purpose for which it was sent by Xyratex. No further rights > or licenses are granted to use such information. If you are not the intended > recipient of this message, please notify the sender by return and delete it. > You may not use, copy, disclose or rely on the information contained in it. > > Internet email is susceptible to data corruption, interception and > unauthorised amendment for which Xyratex does not accept liability. While we > have taken reasonable precautions to ensure that this email is free of viruses, > Xyratex does not accept liability for the presence of any computer viruses in > this email, nor for any losses caused as a result of viruses. > > Xyratex Technology Limited (03134912), Registered in England & Wales, > Registered Office, Langstone Road, Havant, Hampshire, PO9 1SA. > > The Xyratex group of companies also includes, Xyratex Ltd, registered in > Bermuda, Xyratex International Inc, registered in California, Xyratex > (Malaysia) Sdn Bhd registered in Malaysia, Xyratex Technology (Wuxi) Co Ltd > registered in The People's Republic of China and Xyratex Japan Limited > registered in Japan. > ______________________________________________________________________ > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-01-19 7:37 ` Jack Wang @ 2012-01-19 13:46 ` Mark Salyzyn 2012-01-19 14:09 ` jack_wang 0 siblings, 1 reply; 10+ messages in thread From: Mark Salyzyn @ 2012-01-19 13:46 UTC (permalink / raw) To: Jack Wang Cc: Mark Salyzyn, 'linux-scsi', 'crystal_yu', 'lindar_liu', 'James Bottomley' The user that needed this was PMC engineering, you see they needed the event log, along with their proprietary <sigh> forensics, to debug a failure on the SAS bus and report that back to us. The test was their satisfaction when they provided a response to the trouble request with an accurate portrayal of the root cause. My unit test (more than a year ago) was to run a system for a day with a high event level, fiddling cables, then pull the events, watching for the boundary condition (stutter of the last entry). Content was otherwise opaque to me. ToDo is to add a 'write' to the node to reset the outgoing pointer and to add a string describing the events as part of the numerical data; but there is no impetuous for that right now. Sincerely -- Mark Salyzyn On Jan 19, 2012, at 2:37 AM, Jack Wang wrote: >> The pm8001 driver event log currently only reports the current firmware > event >> log index and the first entry (if log has not wrapped). The enclosed patch >> enhances the event log retrieved to sequentially return all un-retrieved >> entries until hitting the last, from then on reporting the last entry. The >> format has been changed in a parseable fashion reporting some of the > interpreted >> fields; the entry sequence number can be used to determine the boundary >> condition when the last entry is retrieved. >> >> Formerly, when the logging level is changed at runtime, it does not > transmit >> the updated level to the firmware. We also added a set of insmod > parameters >> so that the logging level may be set at initialization time. >> > [Jack Wang] Patch is welcome. >> As this disrupts backward compatibility, we look forward to community > comments. >> Also, once an earlier log entry is retrieved, it is the expectation of the >> logging/forensic program(?) to record them as they can not be retrieved > again. >> Please keep in mind that the first event log entry as previously reported > by >> the existing code becomes somewhat moot if new entries form. >> > [Jack Wang] I wonder what user need this function? How do you test this > function? > >> Signed-off-by: mark_salyzyn@xyratex.com >> Cc: jack_wang@usish.com >> Cc: JBottomley@parallels.com >> Cc: crystal_yu@usish.com >> Cc: john_gong@usish.com >> Cc: lindar_liu <lindar_liu@usish.com> > > [Jack Wang] > Acked-by: Jack Wang <jack_wang@usish.com> > Thanks. > >> >> diff --git a/drivers/scsi/pm8001/pm8001_ctl.c >> b/drivers/scsi/pm8001/pm8001_ctl.c >> index 45bc197..3fe3d93 100644 >> --- a/drivers/scsi/pm8001/pm8001_ctl.c >> +++ b/drivers/scsi/pm8001/pm8001_ctl.c >> @@ -228,46 +228,140 @@ static ssize_t > pm8001_ctl_logging_level_store(struct >> device *cdev, >> return -EINVAL; >> >> pm8001_ha->logging_level = val; >> + pm8001_update_main_config_table(pm8001_ha); >> return strlen(buf); >> } >> >> static DEVICE_ATTR(logging_level, S_IRUGO | S_IWUSR, >> pm8001_ctl_logging_level_show, pm8001_ctl_logging_level_store); >> + >> /** >> - * pm8001_ctl_aap_log_show - aap1 event log >> + * pm8001_readlog - generic routine to read a single event log entry >> + * @header: a pointer to the event log base header >> + * @entry: a pointer to copy the latest event log into >> + * @consumer_index: a current local index pointing to the last oldest > entry >> + * retrieved. Automatically incremented and wrapped. >> + * @return: A copied entry content and a status. Status return of zero > means >> + * more entries available, and the consumer index is incremented > and >> + * wrapped. A positive status return means last entry, the > consumer >> + * index may be incremented depending of approached or at the > last >> + * entry. Status can also return a negative error code. It is > the >> + * responsibility of the caller to confirm the validity of the > copied >> + * event information. >> + */ >> +int pm8001_readlog( >> + struct eventlog_header *header, >> + struct eventlog_entry *entry, >> + u32 *consumer_index) >> +{ >> + unsigned maximum_index; >> + unsigned producer_index; >> + >> + /* Validity of header */ >> + if ((header->offset != sizeof(*header)) || >> + (header->entry_size != sizeof(*entry)) || >> + ((header->signature != EVENTLOG_HEADER_SIGNATURE_AAP1) && >> + (header->signature != EVENTLOG_HEADER_SIGNATURE_IOP))) >> + return -EINVAL; >> + /* Validity of hardware indexii */ >> + maximum_index = header->size / sizeof(*entry); >> + if ((maximum_index <= header->producer_index) || >> + (maximum_index <= header->consumer_index)) >> + return -EINVAL; >> + producer_index = header->producer_index; >> + if (producer_index == 0) >> + producer_index = maximum_index - 1; >> + else >> + --producer_index; >> + /* Validity of current consumer index */ >> + if (((producer_index < header->consumer_index) ? >> + ((producer_index < *consumer_index) && >> + (*consumer_index < header->consumer_index)) : >> + ((producer_index < *consumer_index) || >> + (*consumer_index < header->consumer_index))) || >> + (*consumer_index >= maximum_index)) >> + *consumer_index = header->consumer_index; >> + *entry = ((struct eventlog_entry *)(header + 1))[*consumer_index]; >> + if (*consumer_index != producer_index) { >> + ++(*consumer_index); >> + if (*consumer_index >= maximum_index) >> + *consumer_index = 0; >> + /* More entries? */ >> + return *consumer_index == producer_index; >> + } >> + return 2; /* Already at Last Entry */ >> +} >> + >> +/** >> + * pm8001_validlog - generic routine to validate a single event log entry >> + * @entry: a pointer to the event log entry >> + * @return: zero if not valid >> + */ >> +static inline int pm8001_validlog(struct eventlog_entry *entry) >> +{ >> + return ((entry->size <= (sizeof(entry->log) / > sizeof(entry->log[0]))) && >> + ((entry->timestamp_upper != 0) || >> + (entry->timestamp_lower != 0))); >> +} >> + >> +/** >> + * pm8001_ctl_log_show - event log >> * @cdev: pointer to embedded class device >> * @buf: the buffer returned >> + * @ind: The region index (AAP1 or IOP) >> * >> * A sysfs 'read-only' shost attribute. >> */ >> -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >> - struct device_attribute *attr, char *buf) >> +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int > ind) >> { >> struct Scsi_Host *shost = class_to_shost(cdev); >> struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >> struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >> + struct eventlog_entry entry; >> + char *str = buf; >> int i; >> -#define AAP1_MEMMAP(r, c) \ >> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 > \ >> - + (c))) >> >> - char *str = buf; >> - int max = 2; >> - for (i = 0; i < max; i++) { >> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x > 0x%08x" >> - "0x%08x 0x%08x\n", >> - AAP1_MEMMAP(i, 0), >> - AAP1_MEMMAP(i, 4), >> - AAP1_MEMMAP(i, 8), >> - AAP1_MEMMAP(i, 12), >> - AAP1_MEMMAP(i, 16), >> - AAP1_MEMMAP(i, 20), >> - AAP1_MEMMAP(i, 24), >> - AAP1_MEMMAP(i, 28)); >> + memset(&entry, 0, sizeof(entry)); >> + do { >> + i = pm8001_readlog( >> + (struct eventlog_header *)( >> + pm8001_ha->memoryMap.region[ind].virt_ptr), >> + &entry, >> + (ind == AAP1) ? >> + &pm8001_ha->aap1_consumer : >> + &pm8001_ha->iop_consumer); >> + } while ((i == 0) && !pm8001_validlog(&entry)); >> + if ((i >= 0) && pm8001_validlog(&entry)) { >> + u32 *lp; >> + unsigned long long timestamp = >> + (((unsigned long long)entry.timestamp_upper) << 32) > | >> + entry.timestamp_lower; >> + str += snprintf(str, PAGE_SIZE - (str - buf), >> + "%u %llu.%09llu %u", >> + entry.severity, >> + timestamp / (1000000000 / 8), >> + (timestamp % (1000000000 / 8)) * 8, >> + entry.sequence); >> + for (lp = entry.log, i = entry.size; i > 0; --i) >> + str += snprintf(str, PAGE_SIZE - (str - buf), >> + " 0x%08x", *(lp++)); >> + str += snprintf(str, PAGE_SIZE - (str - buf), "\n"); >> } >> - >> return str - buf; >> } >> + >> +/** >> + * pm8001_ctl_aap_log_show - aap1 event log >> + * @cdev: pointer to embedded class device >> + * @buf: the buffer returned >> + * >> + * A sysfs 'read-only' shost attribute. >> + */ >> +static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >> + struct device_attribute *attr, char *buf) >> +{ >> + return pm8001_ctl_log_show(cdev, buf, AAP1); >> +} >> static DEVICE_ATTR(aap_log, S_IRUGO, pm8001_ctl_aap_log_show, NULL); >> /** >> * pm8001_ctl_aap_log_show - IOP event log >> @@ -279,29 +373,7 @@ static DEVICE_ATTR(aap_log, S_IRUGO, >> pm8001_ctl_aap_log_show, NULL); >> static ssize_t pm8001_ctl_iop_log_show(struct device *cdev, >> struct device_attribute *attr, char *buf) >> { >> - struct Scsi_Host *shost = class_to_shost(cdev); >> - struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >> - struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >> -#define IOP_MEMMAP(r, c) \ >> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[IOP].virt_ptr + (r) * 32 > \ >> - + (c))) >> - int i; >> - char *str = buf; >> - int max = 2; >> - for (i = 0; i < max; i++) { >> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x > 0x%08x" >> - "0x%08x 0x%08x\n", >> - IOP_MEMMAP(i, 0), >> - IOP_MEMMAP(i, 4), >> - IOP_MEMMAP(i, 8), >> - IOP_MEMMAP(i, 12), >> - IOP_MEMMAP(i, 16), >> - IOP_MEMMAP(i, 20), >> - IOP_MEMMAP(i, 24), >> - IOP_MEMMAP(i, 28)); >> - } >> - >> - return str - buf; >> + return pm8001_ctl_log_show(cdev, buf, IOP); >> } >> static DEVICE_ATTR(iop_log, S_IRUGO, pm8001_ctl_iop_log_show, NULL); >> >> diff --git a/drivers/scsi/pm8001/pm8001_hwi.c >> b/drivers/scsi/pm8001/pm8001_hwi.c >> index b7b92f7..b3bd781 100644 >> --- a/drivers/scsi/pm8001/pm8001_hwi.c >> +++ b/drivers/scsi/pm8001/pm8001_hwi.c >> @@ -250,8 +250,8 @@ init_default_table_values(struct pm8001_hba_info >> *pm8001_ha) >> * update_main_config_table - update the main default table to the HBA. >> * @pm8001_ha: our hba card information >> */ >> -static void __devinit >> -update_main_config_table(struct pm8001_hba_info *pm8001_ha) >> +void >> +pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha) >> { >> void __iomem *address = pm8001_ha->main_cfg_tbl_addr; >> pm8001_mw32(address, 0x24, >> @@ -603,7 +603,7 @@ static int __devinit pm8001_chip_init(struct >> pm8001_hba_info *pm8001_ha) >> read_inbnd_queue_table(pm8001_ha); >> read_outbnd_queue_table(pm8001_ha); >> /* update main config table ,inbound table and outbound table */ >> - update_main_config_table(pm8001_ha); >> + pm8001_update_main_config_table(pm8001_ha); >> update_inbnd_queue_table(pm8001_ha, 0); >> update_outbnd_queue_table(pm8001_ha, 0); >> mpi_set_phys_g3_with_ssc(pm8001_ha, 0); >> diff --git a/drivers/scsi/pm8001/pm8001_init.c >> b/drivers/scsi/pm8001/pm8001_init.c >> index c21a216..c638da4 100644 >> --- a/drivers/scsi/pm8001/pm8001_init.c >> +++ b/drivers/scsi/pm8001/pm8001_init.c >> @@ -48,6 +48,8 @@ static const struct pm8001_chip_info pm8001_chips[] = { >> [chip_8001] = { 8, &pm8001_8001_dispatch,}, >> }; >> static int pm8001_id; >> +static int pm8001_logging_level = PM8001_FAIL_LOGGING; >> +static int pm8001_logging_size = PM8001_EVENT_LOG_SIZE; >> >> LIST_HEAD(hba_list); >> >> @@ -212,16 +214,19 @@ static int __devinit pm8001_alloc(struct > pm8001_hba_info >> *pm8001_ha) >> pm8001_ha->tags = kzalloc(PM8001_MAX_CCB, GFP_KERNEL); >> if (!pm8001_ha->tags) >> goto err_out; >> + pm8001_logging_size = ((pm8001_logging_size + 31) / 32) * 32; >> + if (pm8001_logging_size < 64) >> + pm8001_logging_size = 64; >> /* MPI Memory region 1 for AAP Event Log for fw */ >> pm8001_ha->memoryMap.region[AAP1].num_elements = 1; >> - pm8001_ha->memoryMap.region[AAP1].element_size = >> PM8001_EVENT_LOG_SIZE; >> - pm8001_ha->memoryMap.region[AAP1].total_len = PM8001_EVENT_LOG_SIZE; >> + pm8001_ha->memoryMap.region[AAP1].element_size = > pm8001_logging_size; >> + pm8001_ha->memoryMap.region[AAP1].total_len = pm8001_logging_size; >> pm8001_ha->memoryMap.region[AAP1].alignment = 32; >> >> /* MPI Memory region 2 for IOP Event Log for fw */ >> pm8001_ha->memoryMap.region[IOP].num_elements = 1; >> - pm8001_ha->memoryMap.region[IOP].element_size = > PM8001_EVENT_LOG_SIZE; >> - pm8001_ha->memoryMap.region[IOP].total_len = PM8001_EVENT_LOG_SIZE; >> + pm8001_ha->memoryMap.region[IOP].element_size = pm8001_logging_size; >> + pm8001_ha->memoryMap.region[IOP].total_len = pm8001_logging_size; >> pm8001_ha->memoryMap.region[IOP].alignment = 32; >> >> /* MPI Memory region 3 for consumer Index of inbound queues */ >> @@ -242,7 +247,7 @@ static int __devinit pm8001_alloc(struct > pm8001_hba_info >> *pm8001_ha) >> pm8001_ha->memoryMap.region[IB].total_len = 256 * 64; >> pm8001_ha->memoryMap.region[IB].alignment = 64; >> >> - /* MPI Memory region 6 inbound queues */ >> + /* MPI Memory region 6 outbound queues */ >> pm8001_ha->memoryMap.region[OB].num_elements = 256; >> pm8001_ha->memoryMap.region[OB].element_size = 64; >> pm8001_ha->memoryMap.region[OB].total_len = 256 * 64; >> @@ -381,7 +386,7 @@ pm8001_pci_alloc(struct pci_dev *pdev, u32 chip_id, > struct >> Scsi_Host *shost) >> pm8001_ha->sas = sha; >> pm8001_ha->shost = shost; >> pm8001_ha->id = pm8001_id++; >> - pm8001_ha->logging_level = 0x01; >> + pm8001_ha->logging_level = pm8001_logging_level; >> sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); >> #ifdef PM8001_USE_TASKLET >> tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, >> @@ -614,8 +619,8 @@ intx: >> * @ent: pci device id >> * >> * This function is the main initialization function, when register a new >> - * pci driver it is invoked, all struct an hardware initilization should > be >> done >> - * here, also, register interrupt >> + * pci driver it is invoked, all struct and hardware initialization > should >> be >> + * done here, also, register interrupt >> */ >> static int __devinit pm8001_pci_probe(struct pci_dev *pdev, >> const struct pci_device_id *ent) >> @@ -899,6 +904,9 @@ static void __exit pm8001_exit(void) >> destroy_workqueue(pm8001_wq); >> } >> >> +module_param(pm8001_logging_level, int, 0); >> +module_param_named(logging_size, pm8001_logging_size, int, >> S_IRUGO|S_IWUSR); >> +MODULE_PARM_DESC(logging_size, "Event logging buffer size"); >> module_init(pm8001_init); >> module_exit(pm8001_exit); >> >> diff --git a/drivers/scsi/pm8001/pm8001_sas.h >> b/drivers/scsi/pm8001/pm8001_sas.h >> index 93959fe..84d3170 100644 >> --- a/drivers/scsi/pm8001/pm8001_sas.h >> +++ b/drivers/scsi/pm8001/pm8001_sas.h >> @@ -336,6 +336,29 @@ struct outbound_queue_table { >> __le32 producer_index; >> u32 consumer_idx; >> }; >> +struct eventlog_header { >> + __le32 signature; >> +#define EVENTLOG_HEADER_SIGNATURE_AAP1 0x1234AAAA >> +#define EVENTLOG_HEADER_SIGNATURE_IOP 0x5678CCCC >> + __le32 offset; /* sizeof(eventlog_header) */ >> + u32 reserved1; >> + __le32 size; >> + u32 reserved2; >> + __le32 consumer_index; >> + __le32 producer_index; >> + __le32 entry_size; >> +}; >> +struct eventlog_entry { >> + u8 size:3; >> + u8 reserved1:5; >> + u8 reserved2[2]; >> + u8 reserved:4; >> + u8 severity:4; >> + __le32 timestamp_upper; >> + __le32 timestamp_lower; >> + __le32 sequence; >> + __le32 log[4]; >> +}; >> struct pm8001_hba_memspace { >> void __iomem *memvirtaddr; >> u64 membase; >> @@ -383,6 +406,9 @@ struct pm8001_hba_info { >> u32 logging_level; >> u32 fw_status; >> const struct firmware *fw_image; >> + /* Local consumer indexes in support of sysfs event log node */ >> + u32 aap1_consumer; >> + u32 iop_consumer; >> }; >> >> struct pm8001_work { >> @@ -488,6 +514,11 @@ int pm8001_mem_alloc(struct pci_dev *pdev, void >> **virt_addr, >> dma_addr_t *pphys_addr, u32 *pphys_addr_hi, u32 *pphys_addr_lo, >> u32 mem_size, u32 align); >> >> +void pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha); >> +int pm8001_readlog( >> + struct eventlog_header *header, >> + struct eventlog_entry *entry, >> + u32 *consumer_index); >> >> /* ctl shared API */ >> extern struct device_attribute *pm8001_host_attrs[]; ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-01-19 13:46 ` Mark Salyzyn @ 2012-01-19 14:09 ` jack_wang 0 siblings, 0 replies; 10+ messages in thread From: jack_wang @ 2012-01-19 14:09 UTC (permalink / raw) Cc: Mark Salyzyn, linux-scsi, crystal_yu, lindar_liu, James Bottomley >The user that needed this was PMC engineering, you see they needed the event log, along with their proprietary <sigh> forensics, to debug a failure on the SAS bus and report that back to us. The test was their satisfaction when they provided a response to the trouble request with an accurate portrayal of the root cause. Yes, PMC always ask for log to analyze failure in the chip. > >My unit test (more than a year ago) was to run a system for a day with a high event level, fiddling cables, then pull the events, watching for the boundary condition (stutter of the last entry). Content was otherwise opaque to me. Thanks for share, this is very helpful. > >ToDo is to add a 'write' to the node to reset the outgoing pointer and to add a string describing the events as part of the numerical data; but there is no impetuous for that right now. > Yes, I think this is a low priority task. Thanks Jack >Sincerely -- Mark Salyzyn > >On Jan 19, 2012, at 2:37 AM, Jack Wang wrote: > >>> The pm8001 driver event log currently only reports the current firmware >> event >>> log index and the first entry (if log has not wrapped). The enclosed patch >>> enhances the event log retrieved to sequentially return all un-retrieved >>> entries until hitting the last, from then on reporting the last entry. The >>> format has been changed in a parseable fashion reporting some of the >> interpreted >>> fields; the entry sequence number can be used to determine the boundary >>> condition when the last entry is retrieved. >>> >>> Formerly, when the logging level is changed at runtime, it does not >> transmit >>> the updated level to the firmware. We also added a set of insmod >> parameters >>> so that the logging level may be set at initialization time. >>> >> [Jack Wang] Patch is welcome. >>> As this disrupts backward compatibility, we look forward to community >> comments. >>> Also, once an earlier log entry is retrieved, it is the expectation of the >>> logging/forensic program(?) to record them as they can not be retrieved >> again. >>> Please keep in mind that the first event log entry as previously reported >> by >>> the existing code becomes somewhat moot if new entries form. >>> >> [Jack Wang] I wonder what user need this function? How do you test this >> function? >> >>> Signed-off-by: mark_salyzyn@xyratex.com >>> Cc: jack_wang@usish.com >>> Cc: JBottomley@parallels.com >>> Cc: crystal_yu@usish.com >>> Cc: john_gong@usish.com >>> Cc: lindar_liu <lindar_liu@usish.com> >> >> [Jack Wang] >> Acked-by: Jack Wang <jack_wang@usish.com> >> Thanks. >> >>> >>> diff --git a/drivers/scsi/pm8001/pm8001_ctl.c >>> b/drivers/scsi/pm8001/pm8001_ctl.c >>> index 45bc197..3fe3d93 100644 >>> --- a/drivers/scsi/pm8001/pm8001_ctl.c >>> +++ b/drivers/scsi/pm8001/pm8001_ctl.c >>> @@ -228,46 +228,140 @@ static ssize_t >> pm8001_ctl_logging_level_store(struct >>> device *cdev, >>> return -EINVAL; >>> >>> pm8001_ha->logging_level = val; >>> + pm8001_update_main_config_table(pm8001_ha); >>> return strlen(buf); >>> } >>> >>> static DEVICE_ATTR(logging_level, S_IRUGO | S_IWUSR, >>> pm8001_ctl_logging_level_show, pm8001_ctl_logging_level_store); >>> + >>> /** >>> - * pm8001_ctl_aap_log_show - aap1 event log >>> + * pm8001_readlog - generic routine to read a single event log entry >>> + * @header: a pointer to the event log base header >>> + * @entry: a pointer to copy the latest event log into >>> + * @consumer_index: a current local index pointing to the last oldest >> entry >>> + * retrieved. Automatically incremented and wrapped. >>> + * @return: A copied entry content and a status. Status return of zero >> means >>> + * more entries available, and the consumer index is incremented >> and >>> + * wrapped. A positive status return means last entry, the >> consumer >>> + * index may be incremented depending of approached or at the >> last >>> + * entry. Status can also return a negative error code. It is >> the >>> + * responsibility of the caller to confirm the validity of the >> copied >>> + * event information. >>> + */ >>> +int pm8001_readlog( >>> + struct eventlog_header *header, >>> + struct eventlog_entry *entry, >>> + u32 *consumer_index) >>> +{ >>> + unsigned maximum_index; >>> + unsigned producer_index; >>> + >>> + /* Validity of header */ >>> + if ((header->offset != sizeof(*header)) || >>> + (header->entry_size != sizeof(*entry)) || >>> + ((header->signature != EVENTLOG_HEADER_SIGNATURE_AAP1) && >>> + (header->signature != EVENTLOG_HEADER_SIGNATURE_IOP))) >>> + return -EINVAL; >>> + /* Validity of hardware indexii */ >>> + maximum_index = header->size / sizeof(*entry); >>> + if ((maximum_index <= header->producer_index) || >>> + (maximum_index <= header->consumer_index)) >>> + return -EINVAL; >>> + producer_index = header->producer_index; >>> + if (producer_index == 0) >>> + producer_index = maximum_index - 1; >>> + else >>> + --producer_index; >>> + /* Validity of current consumer index */ >>> + if (((producer_index < header->consumer_index) ? >>> + ((producer_index < *consumer_index) && >>> + (*consumer_index < header->consumer_index)) : >>> + ((producer_index < *consumer_index) || >>> + (*consumer_index < header->consumer_index))) || >>> + (*consumer_index >= maximum_index)) >>> + *consumer_index = header->consumer_index; >>> + *entry = ((struct eventlog_entry *)(header + 1))[*consumer_index]; >>> + if (*consumer_index != producer_index) { >>> + ++(*consumer_index); >>> + if (*consumer_index >= maximum_index) >>> + *consumer_index = 0; >>> + /* More entries? */ >>> + return *consumer_index == producer_index; >>> + } >>> + return 2; /* Already at Last Entry */ >>> +} >>> + >>> +/** >>> + * pm8001_validlog - generic routine to validate a single event log entry >>> + * @entry: a pointer to the event log entry >>> + * @return: zero if not valid >>> + */ >>> +static inline int pm8001_validlog(struct eventlog_entry *entry) >>> +{ >>> + return ((entry->size <= (sizeof(entry->log) / >> sizeof(entry->log[0]))) && >>> + ((entry->timestamp_upper != 0) || >>> + (entry->timestamp_lower != 0))); >>> +} >>> + >>> +/** >>> + * pm8001_ctl_log_show - event log >>> * @cdev: pointer to embedded class device >>> * @buf: the buffer returned >>> + * @ind: The region index (AAP1 or IOP) >>> * >>> * A sysfs 'read-only' shost attribute. >>> */ >>> -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >>> - struct device_attribute *attr, char *buf) >>> +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int >> ind) >>> { >>> struct Scsi_Host *shost = class_to_shost(cdev); >>> struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >>> struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >>> + struct eventlog_entry entry; >>> + char *str = buf; >>> int i; >>> -#define AAP1_MEMMAP(r, c) \ >>> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 >> \ >>> - + (c))) >>> >>> - char *str = buf; >>> - int max = 2; >>> - for (i = 0; i < max; i++) { >>> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x >> 0x%08x" >>> - "0x%08x 0x%08x\n", >>> - AAP1_MEMMAP(i, 0), >>> - AAP1_MEMMAP(i, 4), >>> - AAP1_MEMMAP(i, 8), >>> - AAP1_MEMMAP(i, 12), >>> - AAP1_MEMMAP(i, 16), >>> - AAP1_MEMMAP(i, 20), >>> - AAP1_MEMMAP(i, 24), >>> - AAP1_MEMMAP(i, 28)); >>> + memset(&entry, 0, sizeof(entry)); >>> + do { >>> + i = pm8001_readlog( >>> + (struct eventlog_header *)( >>> + pm8001_ha->memoryMap.region[ind].virt_ptr), >>> + &entry, >>> + (ind == AAP1) ? >>> + &pm8001_ha->aap1_consumer : >>> + &pm8001_ha->iop_consumer); >>> + } while ((i == 0) && !pm8001_validlog(&entry)); >>> + if ((i >= 0) && pm8001_validlog(&entry)) { >>> + u32 *lp; >>> + unsigned long long timestamp = >>> + (((unsigned long long)entry.timestamp_upper) << 32) >> | >>> + entry.timestamp_lower; >>> + str += snprintf(str, PAGE_SIZE - (str - buf), >>> + "%u %llu.%09llu %u", >>> + entry.severity, >>> + timestamp / (1000000000 / 8), >>> + (timestamp % (1000000000 / 8)) * 8, >>> + entry.sequence); >>> + for (lp = entry.log, i = entry.size; i > 0; --i) >>> + str += snprintf(str, PAGE_SIZE - (str - buf), >>> + " 0x%08x", *(lp++)); >>> + str += snprintf(str, PAGE_SIZE - (str - buf), "\n"); >>> } >>> - >>> return str - buf; >>> } >>> + >>> +/** >>> + * pm8001_ctl_aap_log_show - aap1 event log >>> + * @cdev: pointer to embedded class device >>> + * @buf: the buffer returned >>> + * >>> + * A sysfs 'read-only' shost attribute. >>> + */ >>> +static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >>> + struct device_attribute *attr, char *buf) >>> +{ >>> + return pm8001_ctl_log_show(cdev, buf, AAP1); >>> +} >>> static DEVICE_ATTR(aap_log, S_IRUGO, pm8001_ctl_aap_log_show, NULL); >>> /** >>> * pm8001_ctl_aap_log_show - IOP event log >>> @@ -279,29 +373,7 @@ static DEVICE_ATTR(aap_log, S_IRUGO, >>> pm8001_ctl_aap_log_show, NULL); >>> static ssize_t pm8001_ctl_iop_log_show(struct device *cdev, >>> struct device_attribute *attr, char *buf) >>> { >>> - struct Scsi_Host *shost = class_to_shost(cdev); >>> - struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >>> - struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >>> -#define IOP_MEMMAP(r, c) \ >>> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[IOP].virt_ptr + (r) * 32 >> \ >>> - + (c))) >>> - int i; >>> - char *str = buf; >>> - int max = 2; >>> - for (i = 0; i < max; i++) { >>> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x >> 0x%08x" >>> - "0x%08x 0x%08x\n", >>> - IOP_MEMMAP(i, 0), >>> - IOP_MEMMAP(i, 4), >>> - IOP_MEMMAP(i, 8), >>> - IOP_MEMMAP(i, 12), >>> - IOP_MEMMAP(i, 16), >>> - IOP_MEMMAP(i, 20), >>> - IOP_MEMMAP(i, 24), >>> - IOP_MEMMAP(i, 28)); >>> - } >>> - >>> - return str - buf; >>> + return pm8001_ctl_log_show(cdev, buf, IOP); >>> } >>> static DEVICE_ATTR(iop_log, S_IRUGO, pm8001_ctl_iop_log_show, NULL); >>> >>> diff --git a/drivers/scsi/pm8001/pm8001_hwi.c >>> b/drivers/scsi/pm8001/pm8001_hwi.c >>> index b7b92f7..b3bd781 100644 >>> --- a/drivers/scsi/pm8001/pm8001_hwi.c >>> +++ b/drivers/scsi/pm8001/pm8001_hwi.c >>> @@ -250,8 +250,8 @@ init_default_table_values(struct pm8001_hba_info >>> *pm8001_ha) >>> * update_main_config_table - update the main default table to the HBA. >>> * @pm8001_ha: our hba card information >>> */ >>> -static void __devinit >>> -update_main_config_table(struct pm8001_hba_info *pm8001_ha) >>> +void >>> +pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha) >>> { >>> void __iomem *address = pm8001_ha->main_cfg_tbl_addr; >>> pm8001_mw32(address, 0x24, >>> @@ -603,7 +603,7 @@ static int __devinit pm8001_chip_init(struct >>> pm8001_hba_info *pm8001_ha) >>> read_inbnd_queue_table(pm8001_ha); >>> read_outbnd_queue_table(pm8001_ha); >>> /* update main config table ,inbound table and outbound table */ >>> - update_main_config_table(pm8001_ha); >>> + pm8001_update_main_config_table(pm8001_ha); >>> update_inbnd_queue_table(pm8001_ha, 0); >>> update_outbnd_queue_table(pm8001_ha, 0); >>> mpi_set_phys_g3_with_ssc(pm8001_ha, 0); >>> diff --git a/drivers/scsi/pm8001/pm8001_init.c >>> b/drivers/scsi/pm8001/pm8001_init.c >>> index c21a216..c638da4 100644 >>> --- a/drivers/scsi/pm8001/pm8001_init.c >>> +++ b/drivers/scsi/pm8001/pm8001_init.c >>> @@ -48,6 +48,8 @@ static const struct pm8001_chip_info pm8001_chips[] = { >>> [chip_8001] = { 8, &pm8001_8001_dispatch,}, >>> }; >>> static int pm8001_id; >>> +static int pm8001_logging_level = PM8001_FAIL_LOGGING; >>> +static int pm8001_logging_size = PM8001_EVENT_LOG_SIZE; >>> >>> LIST_HEAD(hba_list); >>> >>> @@ -212,16 +214,19 @@ static int __devinit pm8001_alloc(struct >> pm8001_hba_info >>> *pm8001_ha) >>> pm8001_ha->tags = kzalloc(PM8001_MAX_CCB, GFP_KERNEL); >>> if (!pm8001_ha->tags) >>> goto err_out; >>> + pm8001_logging_size = ((pm8001_logging_size + 31) / 32) * 32; >>> + if (pm8001_logging_size < 64) >>> + pm8001_logging_size = 64; >>> /* MPI Memory region 1 for AAP Event Log for fw */ >>> pm8001_ha->memoryMap.region[AAP1].num_elements = 1; >>> - pm8001_ha->memoryMap.region[AAP1].element_size = >>> PM8001_EVENT_LOG_SIZE; >>> - pm8001_ha->memoryMap.region[AAP1].total_len = PM8001_EVENT_LOG_SIZE; >>> + pm8001_ha->memoryMap.region[AAP1].element_size = >> pm8001_logging_size; >>> + pm8001_ha->memoryMap.region[AAP1].total_len = pm8001_logging_size; >>> pm8001_ha->memoryMap.region[AAP1].alignment = 32; >>> >>> /* MPI Memory region 2 for IOP Event Log for fw */ >>> pm8001_ha->memoryMap.region[IOP].num_elements = 1; >>> - pm8001_ha->memoryMap.region[IOP].element_size = >> PM8001_EVENT_LOG_SIZE; >>> - pm8001_ha->memoryMap.region[IOP].total_len = PM8001_EVENT_LOG_SIZE; >>> + pm8001_ha->memoryMap.region[IOP].element_size = pm8001_logging_size; >>> + pm8001_ha->memoryMap.region[IOP].total_len = pm8001_logging_size; >>> pm8001_ha->memoryMap.region[IOP].alignment = 32; >>> >>> /* MPI Memory region 3 for consumer Index of inbound queues */ >>> @@ -242,7 +247,7 @@ static int __devinit pm8001_alloc(struct >> pm8001_hba_info >>> *pm8001_ha) >>> pm8001_ha->memoryMap.region[IB].total_len = 256 * 64; >>> pm8001_ha->memoryMap.region[IB].alignment = 64; >>> >>> - /* MPI Memory region 6 inbound queues */ >>> + /* MPI Memory region 6 outbound queues */ >>> pm8001_ha->memoryMap.region[OB].num_elements = 256; >>> pm8001_ha->memoryMap.region[OB].element_size = 64; >>> pm8001_ha->memoryMap.region[OB].total_len = 256 * 64; >>> @@ -381,7 +386,7 @@ pm8001_pci_alloc(struct pci_dev *pdev, u32 chip_id, >> struct >>> Scsi_Host *shost) >>> pm8001_ha->sas = sha; >>> pm8001_ha->shost = shost; >>> pm8001_ha->id = pm8001_id++; >>> - pm8001_ha->logging_level = 0x01; >>> + pm8001_ha->logging_level = pm8001_logging_level; >>> sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); >>> #ifdef PM8001_USE_TASKLET >>> tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, >>> @@ -614,8 +619,8 @@ intx: >>> * @ent: pci device id >>> * >>> * This function is the main initialization function, when register a new >>> - * pci driver it is invoked, all struct an hardware initilization should >> be >>> done >>> - * here, also, register interrupt >>> + * pci driver it is invoked, all struct and hardware initialization >> should >>> be >>> + * done here, also, register interrupt >>> */ >>> static int __devinit pm8001_pci_probe(struct pci_dev *pdev, >>> const struct pci_device_id *ent) >>> @@ -899,6 +904,9 @@ static void __exit pm8001_exit(void) >>> destroy_workqueue(pm8001_wq); >>> } >>> >>> +module_param(pm8001_logging_level, int, 0); >>> +module_param_named(logging_size, pm8001_logging_size, int, >>> S_IRUGO|S_IWUSR); >>> +MODULE_PARM_DESC(logging_size, "Event logging buffer size"); >>> module_init(pm8001_init); >>> module_exit(pm8001_exit); >>> >>> diff --git a/drivers/scsi/pm8001/pm8001_sas.h >>> b/drivers/scsi/pm8001/pm8001_sas.h >>> index 93959fe..84d3170 100644 >>> --- a/drivers/scsi/pm8001/pm8001_sas.h >>> +++ b/drivers/scsi/pm8001/pm8001_sas.h >>> @@ -336,6 +336,29 @@ struct outbound_queue_table { >>> __le32 producer_index; >>> u32 consumer_idx; >>> }; >>> +struct eventlog_header { >>> + __le32 signature; >>> +#define EVENTLOG_HEADER_SIGNATURE_AAP1 0x1234AAAA >>> +#define EVENTLOG_HEADER_SIGNATURE_IOP 0x5678CCCC >>> + __le32 offset; /* sizeof(eventlog_header) */ >>> + u32 reserved1; >>> + __le32 size; >>> + u32 reserved2; >>> + __le32 consumer_index; >>> + __le32 producer_index; >>> + __le32 entry_size; >>> +}; >>> +struct eventlog_entry { >>> + u8 size:3; >>> + u8 reserved1:5; >>> + u8 reserved2[2]; >>> + u8 reserved:4; >>> + u8 severity:4; >>> + __le32 timestamp_upper; >>> + __le32 timestamp_lower; >>> + __le32 sequence; >>> + __le32 log[4]; >>> +}; >>> struct pm8001_hba_memspace { >>> void __iomem *memvirtaddr; >>> u64 membase; >>> @@ -383,6 +406,9 @@ struct pm8001_hba_info { >>> u32 logging_level; >>> u32 fw_status; >>> const struct firmware *fw_image; >>> + /* Local consumer indexes in support of sysfs event log node */ >>> + u32 aap1_consumer; >>> + u32 iop_consumer; >>> }; >>> >>> struct pm8001_work { >>> @@ -488,6 +514,11 @@ int pm8001_mem_alloc(struct pci_dev *pdev, void >>> **virt_addr, >>> dma_addr_t *pphys_addr, u32 *pphys_addr_hi, u32 *pphys_addr_lo, >>> u32 mem_size, u32 align); >>> >>> +void pm8001_update_main_config_table(struct pm8001_hba_info *pm8001_ha); >>> +int pm8001_readlog( >>> + struct eventlog_header *header, >>> + struct eventlog_entry *entry, >>> + u32 *consumer_index); >>> >>> /* ctl shared API */ >>> extern struct device_attribute *pm8001_host_attrs[]; > >-- >To unsubscribe from this list: send the line "unsubscribe linux-scsi" in >the body of a message to majordomo@vger.kernel.org >More majordomo info at http://vger.kernel.org/majordomo-info.html > >__________ Information from ESET NOD32 Antivirus, version of virus signature database 5659 (20101129) __________ > >The message was checked by ESET NOD32 Antivirus. > >http://www.eset.com > > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-01-18 15:48 [PATCH] pm8001: enhance firmware event log retrieval Mark Salyzyn 2012-01-19 7:37 ` Jack Wang @ 2012-02-18 15:20 ` James Bottomley 2012-02-19 6:55 ` jack_wang 1 sibling, 1 reply; 10+ messages in thread From: James Bottomley @ 2012-02-18 15:20 UTC (permalink / raw) To: Mark Salyzyn; +Cc: linux-scsi, Jack Wang, crystal_yu, lindar_liu On Wed, 2012-01-18 at 10:48 -0500, Mark Salyzyn wrote: > -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, > - struct device_attribute *attr, char *buf) > +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) > { > struct Scsi_Host *shost = class_to_shost(cdev); > struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); > struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; > + struct eventlog_entry entry; > + char *str = buf; > int i; > -#define AAP1_MEMMAP(r, c) \ > - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 \ > - + (c))) > > - char *str = buf; > - int max = 2; > - for (i = 0; i < max; i++) { > - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" > - "0x%08x 0x%08x\n", > - AAP1_MEMMAP(i, 0), > - AAP1_MEMMAP(i, 4), > - AAP1_MEMMAP(i, 8), > - AAP1_MEMMAP(i, 12), > - AAP1_MEMMAP(i, 16), > - AAP1_MEMMAP(i, 20), > - AAP1_MEMMAP(i, 24), > - AAP1_MEMMAP(i, 28)); > + memset(&entry, 0, sizeof(entry)); > + do { > + i = pm8001_readlog( > + (struct eventlog_header *)( > + pm8001_ha->memoryMap.region[ind].virt_ptr), > + &entry, > + (ind == AAP1) ? > + &pm8001_ha->aap1_consumer : > + &pm8001_ha->iop_consumer); > + } while ((i == 0) && !pm8001_validlog(&entry)); > + if ((i >= 0) && pm8001_validlog(&entry)) { > + u32 *lp; > + unsigned long long timestamp = > + (((unsigned long long)entry.timestamp_upper) << 32) | > + entry.timestamp_lower; > + str += snprintf(str, PAGE_SIZE - (str - buf), > + "%u %llu.%09llu %u", > + entry.severity, > + timestamp / (1000000000 / 8), > + (timestamp % (1000000000 / 8)) * 8, > + entry.sequence); You can't do this. It produces this error on a 32 bit compile: MODPOST 1540 modules ERROR: "__udivdi3" [drivers/scsi/pm8001/pm8001.ko] undefined! ERROR: "__umoddi3" [drivers/scsi/pm8001/pm8001.ko] undefined! make[1]: *** [__modpost] Error 1 make: *** [drivers/scsi/pm8001/pm8001.ko] Error 2 because the compiler can't natively do 64 bit division. The fix is something like this (compile tested only) James --- diff --git a/drivers/scsi/pm8001/pm8001_ctl.c b/drivers/scsi/pm8001/pm8001_ctl.c index 3fe3d93..0edfe86 100644 --- a/drivers/scsi/pm8001/pm8001_ctl.c +++ b/drivers/scsi/pm8001/pm8001_ctl.c @@ -335,12 +335,13 @@ static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) u32 *lp; unsigned long long timestamp = (((unsigned long long)entry.timestamp_upper) << 32) | - entry.timestamp_lower; + entry.timestamp_lower, remainder; + remainder = do_div(timestamp, 1000000000 / 8) * 8; str += snprintf(str, PAGE_SIZE - (str - buf), "%u %llu.%09llu %u", entry.severity, - timestamp / (1000000000 / 8), - (timestamp % (1000000000 / 8)) * 8, + timestamp, + remainder, entry.sequence); for (lp = entry.log, i = entry.size; i > 0; --i) str += snprintf(str, PAGE_SIZE - (str - buf), ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-02-18 15:20 ` James Bottomley @ 2012-02-19 6:55 ` jack_wang 2012-02-20 16:04 ` Mark Salyzyn 0 siblings, 1 reply; 10+ messages in thread From: jack_wang @ 2012-02-19 6:55 UTC (permalink / raw) To: James Bottomley, Mark Salyzyn; +Cc: linux-scsi, crystal_yu, lindar_liu James, Thanks for fix, looks OK for me. -------------- jack_wang >On Wed, 2012-01-18 at 10:48 -0500, Mark Salyzyn wrote: >> -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >> - struct device_attribute *attr, char *buf) >> +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) >> { >> struct Scsi_Host *shost = class_to_shost(cdev); >> struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >> struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >> + struct eventlog_entry entry; >> + char *str = buf; >> int i; >> -#define AAP1_MEMMAP(r, c) \ >> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 \ >> - + (c))) >> >> - char *str = buf; >> - int max = 2; >> - for (i = 0; i < max; i++) { >> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" >> - "0x%08x 0x%08x\n", >> - AAP1_MEMMAP(i, 0), >> - AAP1_MEMMAP(i, 4), >> - AAP1_MEMMAP(i, 8), >> - AAP1_MEMMAP(i, 12), >> - AAP1_MEMMAP(i, 16), >> - AAP1_MEMMAP(i, 20), >> - AAP1_MEMMAP(i, 24), >> - AAP1_MEMMAP(i, 28)); >> + memset(&entry, 0, sizeof(entry)); >> + do { >> + i = pm8001_readlog( >> + (struct eventlog_header *)( >> + pm8001_ha->memoryMap.region[ind].virt_ptr), >> + &entry, >> + (ind == AAP1) ? >> + &pm8001_ha->aap1_consumer : >> + &pm8001_ha->iop_consumer); >> + } while ((i == 0) && !pm8001_validlog(&entry)); >> + if ((i >= 0) && pm8001_validlog(&entry)) { >> + u32 *lp; >> + unsigned long long timestamp = >> + (((unsigned long long)entry.timestamp_upper) << 32) | >> + entry.timestamp_lower; >> + str += snprintf(str, PAGE_SIZE - (str - buf), >> + "%u %llu.%09llu %u", >> + entry.severity, >> + timestamp / (1000000000 / 8), >> + (timestamp % (1000000000 / 8)) * 8, >> + entry.sequence); > >You can't do this. It produces this error on a 32 bit compile: > > MODPOST 1540 modules >ERROR: "__udivdi3" [drivers/scsi/pm8001/pm8001.ko] undefined! >ERROR: "__umoddi3" [drivers/scsi/pm8001/pm8001.ko] undefined! >make[1]: *** [__modpost] Error 1 >make: *** [drivers/scsi/pm8001/pm8001.ko] Error 2 > >because the compiler can't natively do 64 bit division. > >The fix is something like this (compile tested only) > >James > >--- > >diff --git a/drivers/scsi/pm8001/pm8001_ctl.c b/drivers/scsi/pm8001/pm8001_ctl.c >index 3fe3d93..0edfe86 100644 >--- a/drivers/scsi/pm8001/pm8001_ctl.c >+++ b/drivers/scsi/pm8001/pm8001_ctl.c >@@ -335,12 +335,13 @@ static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) > u32 *lp; > unsigned long long timestamp = > (((unsigned long long)entry.timestamp_upper) << 32) | >- entry.timestamp_lower; >+ entry.timestamp_lower, remainder; >+ remainder = do_div(timestamp, 1000000000 / 8) * 8; > str += snprintf(str, PAGE_SIZE - (str - buf), > "%u %llu.%09llu %u", > entry.severity, >- timestamp / (1000000000 / 8), >- (timestamp % (1000000000 / 8)) * 8, >+ timestamp, >+ remainder, > entry.sequence); > for (lp = entry.log, i = entry.size; i > 0; --i) > str += snprintf(str, PAGE_SIZE - (str - buf), > > >__________ Information from ESET NOD32 Antivirus, version of virus signature database 5659 (20101129) __________ > >The message was checked by ESET NOD32 Antivirus. > >http://www.eset.com > > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-02-19 6:55 ` jack_wang @ 2012-02-20 16:04 ` Mark Salyzyn 2012-02-20 16:13 ` James Bottomley 2012-02-20 16:31 ` Mark Salyzyn 0 siblings, 2 replies; 10+ messages in thread From: Mark Salyzyn @ 2012-02-20 16:04 UTC (permalink / raw) To: jack_wang Cc: Mark Salyzyn, James Bottomley, linux-scsi, crystal_yu, lindar_liu Thanks James! Sincerely -- Mark Salyzyn On Feb 19, 2012, at 1:55 AM, jack_wang wrote: > James, > > Thanks for fix, looks OK for me. > > > > -------------- > jack_wang >> On Wed, 2012-01-18 at 10:48 -0500, Mark Salyzyn wrote: >>> -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >>> - struct device_attribute *attr, char *buf) >>> +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) >>> { >>> struct Scsi_Host *shost = class_to_shost(cdev); >>> struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >>> struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >>> + struct eventlog_entry entry; >>> + char *str = buf; >>> int i; >>> -#define AAP1_MEMMAP(r, c) \ >>> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 \ >>> - + (c))) >>> >>> - char *str = buf; >>> - int max = 2; >>> - for (i = 0; i < max; i++) { >>> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" >>> - "0x%08x 0x%08x\n", >>> - AAP1_MEMMAP(i, 0), >>> - AAP1_MEMMAP(i, 4), >>> - AAP1_MEMMAP(i, 8), >>> - AAP1_MEMMAP(i, 12), >>> - AAP1_MEMMAP(i, 16), >>> - AAP1_MEMMAP(i, 20), >>> - AAP1_MEMMAP(i, 24), >>> - AAP1_MEMMAP(i, 28)); >>> + memset(&entry, 0, sizeof(entry)); >>> + do { >>> + i = pm8001_readlog( >>> + (struct eventlog_header *)( >>> + pm8001_ha->memoryMap.region[ind].virt_ptr), >>> + &entry, >>> + (ind == AAP1) ? >>> + &pm8001_ha->aap1_consumer : >>> + &pm8001_ha->iop_consumer); >>> + } while ((i == 0) && !pm8001_validlog(&entry)); >>> + if ((i >= 0) && pm8001_validlog(&entry)) { >>> + u32 *lp; >>> + unsigned long long timestamp = >>> + (((unsigned long long)entry.timestamp_upper) << 32) | >>> + entry.timestamp_lower; >>> + str += snprintf(str, PAGE_SIZE - (str - buf), >>> + "%u %llu.%09llu %u", >>> + entry.severity, >>> + timestamp / (1000000000 / 8), >>> + (timestamp % (1000000000 / 8)) * 8, >>> + entry.sequence); >> >> You can't do this. It produces this error on a 32 bit compile: >> >> MODPOST 1540 modules >> ERROR: "__udivdi3" [drivers/scsi/pm8001/pm8001.ko] undefined! >> ERROR: "__umoddi3" [drivers/scsi/pm8001/pm8001.ko] undefined! >> make[1]: *** [__modpost] Error 1 >> make: *** [drivers/scsi/pm8001/pm8001.ko] Error 2 >> >> because the compiler can't natively do 64 bit division. >> >> The fix is something like this (compile tested only) >> >> James >> >> --- >> >> diff --git a/drivers/scsi/pm8001/pm8001_ctl.c b/drivers/scsi/pm8001/pm8001_ctl.c >> index 3fe3d93..0edfe86 100644 >> --- a/drivers/scsi/pm8001/pm8001_ctl.c >> +++ b/drivers/scsi/pm8001/pm8001_ctl.c >> @@ -335,12 +335,13 @@ static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) >> u32 *lp; >> unsigned long long timestamp = >> (((unsigned long long)entry.timestamp_upper) << 32) | >> - entry.timestamp_lower; >> + entry.timestamp_lower, remainder; >> + remainder = do_div(timestamp, 1000000000 / 8) * 8; >> str += snprintf(str, PAGE_SIZE - (str - buf), >> "%u %llu.%09llu %u", >> entry.severity, >> - timestamp / (1000000000 / 8), >> - (timestamp % (1000000000 / 8)) * 8, >> + timestamp, >> + remainder, >> entry.sequence); >> for (lp = entry.log, i = entry.size; i > 0; --i) >> str += snprintf(str, PAGE_SIZE - (str - buf), >> >> >> __________ Information from ESET NOD32 Antivirus, version of virus signature database 5659 (20101129) __________ >> >> The message was checked by ESET NOD32 Antivirus. >> >> http://www.eset.com >> >> ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-02-20 16:04 ` Mark Salyzyn @ 2012-02-20 16:13 ` James Bottomley 2012-02-20 16:31 ` Mark Salyzyn 1 sibling, 0 replies; 10+ messages in thread From: James Bottomley @ 2012-02-20 16:13 UTC (permalink / raw) To: Mark Salyzyn; +Cc: jack_wang, linux-scsi, crystal_yu, lindar_liu On Mon, 2012-02-20 at 11:04 -0500, Mark Salyzyn wrote: > Thanks James! > > Sincerely -- Mark Salyzyn Does that mean you've tested what I propose and it works? James ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-02-20 16:04 ` Mark Salyzyn 2012-02-20 16:13 ` James Bottomley @ 2012-02-20 16:31 ` Mark Salyzyn 2012-02-20 16:43 ` James Bottomley 1 sibling, 1 reply; 10+ messages in thread From: Mark Salyzyn @ 2012-02-20 16:31 UTC (permalink / raw) To: jack_wang Cc: Mark Salyzyn, James Bottomley, linux-scsi, crystal_yu, lindar_liu No testing, I agreed on the appearance, make excellent sense and can see no problem with it. I have NOT tested it (Xyratex has no interest in 32-bit systems, this turns into a one-of with no Q/A backing). I will put it in my own queue for testing and submit a fix if I feel it is necessary (my WAG on risk of that is 0.000234% ;-/ ) Sincerely -- Mark Salyzyn On Feb 20, 2012, at 11:04 AM, Mark Salyzyn wrote: > Thanks James! > > Sincerely -- Mark Salyzyn > > On Feb 19, 2012, at 1:55 AM, jack_wang wrote: > >> James, >> >> Thanks for fix, looks OK for me. >> >> >> >> -------------- >> jack_wang >>> On Wed, 2012-01-18 at 10:48 -0500, Mark Salyzyn wrote: >>>> -static ssize_t pm8001_ctl_aap_log_show(struct device *cdev, >>>> - struct device_attribute *attr, char *buf) >>>> +static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) >>>> { >>>> struct Scsi_Host *shost = class_to_shost(cdev); >>>> struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); >>>> struct pm8001_hba_info *pm8001_ha = sha->lldd_ha; >>>> + struct eventlog_entry entry; >>>> + char *str = buf; >>>> int i; >>>> -#define AAP1_MEMMAP(r, c) \ >>>> - (*(u32 *)((u8*)pm8001_ha->memoryMap.region[AAP1].virt_ptr + (r) * 32 \ >>>> - + (c))) >>>> >>>> - char *str = buf; >>>> - int max = 2; >>>> - for (i = 0; i < max; i++) { >>>> - str += sprintf(str, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x" >>>> - "0x%08x 0x%08x\n", >>>> - AAP1_MEMMAP(i, 0), >>>> - AAP1_MEMMAP(i, 4), >>>> - AAP1_MEMMAP(i, 8), >>>> - AAP1_MEMMAP(i, 12), >>>> - AAP1_MEMMAP(i, 16), >>>> - AAP1_MEMMAP(i, 20), >>>> - AAP1_MEMMAP(i, 24), >>>> - AAP1_MEMMAP(i, 28)); >>>> + memset(&entry, 0, sizeof(entry)); >>>> + do { >>>> + i = pm8001_readlog( >>>> + (struct eventlog_header *)( >>>> + pm8001_ha->memoryMap.region[ind].virt_ptr), >>>> + &entry, >>>> + (ind == AAP1) ? >>>> + &pm8001_ha->aap1_consumer : >>>> + &pm8001_ha->iop_consumer); >>>> + } while ((i == 0) && !pm8001_validlog(&entry)); >>>> + if ((i >= 0) && pm8001_validlog(&entry)) { >>>> + u32 *lp; >>>> + unsigned long long timestamp = >>>> + (((unsigned long long)entry.timestamp_upper) << 32) | >>>> + entry.timestamp_lower; >>>> + str += snprintf(str, PAGE_SIZE - (str - buf), >>>> + "%u %llu.%09llu %u", >>>> + entry.severity, >>>> + timestamp / (1000000000 / 8), >>>> + (timestamp % (1000000000 / 8)) * 8, >>>> + entry.sequence); >>> >>> You can't do this. It produces this error on a 32 bit compile: >>> >>> MODPOST 1540 modules >>> ERROR: "__udivdi3" [drivers/scsi/pm8001/pm8001.ko] undefined! >>> ERROR: "__umoddi3" [drivers/scsi/pm8001/pm8001.ko] undefined! >>> make[1]: *** [__modpost] Error 1 >>> make: *** [drivers/scsi/pm8001/pm8001.ko] Error 2 >>> >>> because the compiler can't natively do 64 bit division. >>> >>> The fix is something like this (compile tested only) >>> >>> James >>> >>> --- >>> >>> diff --git a/drivers/scsi/pm8001/pm8001_ctl.c b/drivers/scsi/pm8001/pm8001_ctl.c >>> index 3fe3d93..0edfe86 100644 >>> --- a/drivers/scsi/pm8001/pm8001_ctl.c >>> +++ b/drivers/scsi/pm8001/pm8001_ctl.c >>> @@ -335,12 +335,13 @@ static ssize_t pm8001_ctl_log_show(struct device *cdev, char *buf, int ind) >>> u32 *lp; >>> unsigned long long timestamp = >>> (((unsigned long long)entry.timestamp_upper) << 32) | >>> - entry.timestamp_lower; >>> + entry.timestamp_lower, remainder; >>> + remainder = do_div(timestamp, 1000000000 / 8) * 8; >>> str += snprintf(str, PAGE_SIZE - (str - buf), >>> "%u %llu.%09llu %u", >>> entry.severity, >>> - timestamp / (1000000000 / 8), >>> - (timestamp % (1000000000 / 8)) * 8, >>> + timestamp, >>> + remainder, >>> entry.sequence); >>> for (lp = entry.log, i = entry.size; i > 0; --i) >>> str += snprintf(str, PAGE_SIZE - (str - buf), >>> >>> >>> __________ Information from ESET NOD32 Antivirus, version of virus signature database 5659 (20101129) __________ >>> >>> The message was checked by ESET NOD32 Antivirus. >>> >>> http://www.eset.com >>> >>> > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] pm8001: enhance firmware event log retrieval 2012-02-20 16:31 ` Mark Salyzyn @ 2012-02-20 16:43 ` James Bottomley 0 siblings, 0 replies; 10+ messages in thread From: James Bottomley @ 2012-02-20 16:43 UTC (permalink / raw) To: Mark Salyzyn; +Cc: jack_wang, linux-scsi, crystal_yu, lindar_liu On Mon, 2012-02-20 at 11:31 -0500, Mark Salyzyn wrote: > No testing, I agreed on the appearance, make excellent sense and can > see no problem with it. I have NOT tested it (Xyratex has no interest > in 32-bit systems, this turns into a one-of with no Q/A backing). I > will put it in my own queue for testing and submit a fix if I feel it > is necessary (my WAG on risk of that is 0.000234% ;-/ ) You don't need to test it on a 32 bit system ... just make sure that it gives a correct log entry on a 64 bit one (i.e. that I got all the factors correct). James ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2012-02-20 16:43 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2012-01-18 15:48 [PATCH] pm8001: enhance firmware event log retrieval Mark Salyzyn 2012-01-19 7:37 ` Jack Wang 2012-01-19 13:46 ` Mark Salyzyn 2012-01-19 14:09 ` jack_wang 2012-02-18 15:20 ` James Bottomley 2012-02-19 6:55 ` jack_wang 2012-02-20 16:04 ` Mark Salyzyn 2012-02-20 16:13 ` James Bottomley 2012-02-20 16:31 ` Mark Salyzyn 2012-02-20 16:43 ` James Bottomley
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox