| /* |
| ******************************************************************************* |
| ** O.S : Linux |
| ** FILE NAME : arcmsr_attr.c |
| ** BY : Nick Cheng |
| ** Description: attributes exported to sysfs and device host |
| ******************************************************************************* |
| ** Copyright (C) 2002 - 2005, Areca Technology Corporation All rights reserved |
| ** |
| ** Web site: www.areca.com.tw |
| ** E-mail: support@areca.com.tw |
| ** |
| ** This program is free software; you can redistribute it and/or modify |
| ** it under the terms of the GNU General Public License version 2 as |
| ** published by the Free Software Foundation. |
| ** This program is distributed in the hope that it will be useful, |
| ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
| ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| ** GNU General Public License for more details. |
| ******************************************************************************* |
| ** Redistribution and use in source and binary forms, with or without |
| ** modification, are permitted provided that the following conditions |
| ** are met: |
| ** 1. Redistributions of source code must retain the above copyright |
| ** notice, this list of conditions and the following disclaimer. |
| ** 2. Redistributions in binary form must reproduce the above copyright |
| ** notice, this list of conditions and the following disclaimer in the |
| ** documentation and/or other materials provided with the distribution. |
| ** 3. The name of the author may not be used to endorse or promote products |
| ** derived from this software without specific prior written permission. |
| ** |
| ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING,BUT |
| ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION)HOWEVER CAUSED AND ON ANY |
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| ** (INCLUDING NEGLIGENCE OR OTHERWISE)ARISING IN ANY WAY OUT OF THE USE OF |
| ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| ******************************************************************************* |
| ** For history of changes, see Documentation/scsi/ChangeLog.arcmsr |
| ** Firmware Specification, see Documentation/scsi/arcmsr_spec.rst |
| ******************************************************************************* |
| */ |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/errno.h> |
| #include <linux/delay.h> |
| #include <linux/pci.h> |
| #include <linux/circ_buf.h> |
| |
| #include <scsi/scsi_cmnd.h> |
| #include <scsi/scsi_device.h> |
| #include <scsi/scsi_host.h> |
| #include <scsi/scsi_transport.h> |
| #include "arcmsr.h" |
| |
| static ssize_t arcmsr_sysfs_iop_message_read(struct file *filp, |
| struct kobject *kobj, |
| struct bin_attribute *bin, |
| char *buf, loff_t off, |
| size_t count) |
| { |
| struct device *dev = container_of(kobj,struct device,kobj); |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = (struct AdapterControlBlock *) host->hostdata; |
| uint8_t *ptmpQbuffer; |
| int32_t allxfer_len = 0; |
| unsigned long flags; |
| |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EACCES; |
| |
| /* do message unit read. */ |
| ptmpQbuffer = (uint8_t *)buf; |
| spin_lock_irqsave(&acb->rqbuffer_lock, flags); |
| if (acb->rqbuf_getIndex != acb->rqbuf_putIndex) { |
| unsigned int tail = acb->rqbuf_getIndex; |
| unsigned int head = acb->rqbuf_putIndex; |
| unsigned int cnt_to_end = CIRC_CNT_TO_END(head, tail, ARCMSR_MAX_QBUFFER); |
| |
| allxfer_len = CIRC_CNT(head, tail, ARCMSR_MAX_QBUFFER); |
| if (allxfer_len > ARCMSR_API_DATA_BUFLEN) |
| allxfer_len = ARCMSR_API_DATA_BUFLEN; |
| |
| if (allxfer_len <= cnt_to_end) |
| memcpy(ptmpQbuffer, acb->rqbuffer + tail, allxfer_len); |
| else { |
| memcpy(ptmpQbuffer, acb->rqbuffer + tail, cnt_to_end); |
| memcpy(ptmpQbuffer + cnt_to_end, acb->rqbuffer, allxfer_len - cnt_to_end); |
| } |
| acb->rqbuf_getIndex = (acb->rqbuf_getIndex + allxfer_len) % ARCMSR_MAX_QBUFFER; |
| } |
| if (acb->acb_flags & ACB_F_IOPDATA_OVERFLOW) { |
| struct QBUFFER __iomem *prbuffer; |
| acb->acb_flags &= ~ACB_F_IOPDATA_OVERFLOW; |
| prbuffer = arcmsr_get_iop_rqbuffer(acb); |
| if (arcmsr_Read_iop_rqbuffer_data(acb, prbuffer) == 0) |
| acb->acb_flags |= ACB_F_IOPDATA_OVERFLOW; |
| } |
| spin_unlock_irqrestore(&acb->rqbuffer_lock, flags); |
| return allxfer_len; |
| } |
| |
| static ssize_t arcmsr_sysfs_iop_message_write(struct file *filp, |
| struct kobject *kobj, |
| struct bin_attribute *bin, |
| char *buf, loff_t off, |
| size_t count) |
| { |
| struct device *dev = container_of(kobj,struct device,kobj); |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = (struct AdapterControlBlock *) host->hostdata; |
| int32_t user_len, cnt2end; |
| uint8_t *pQbuffer, *ptmpuserbuffer; |
| unsigned long flags; |
| |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EACCES; |
| if (count > ARCMSR_API_DATA_BUFLEN) |
| return -EINVAL; |
| /* do message unit write. */ |
| ptmpuserbuffer = (uint8_t *)buf; |
| user_len = (int32_t)count; |
| spin_lock_irqsave(&acb->wqbuffer_lock, flags); |
| if (acb->wqbuf_putIndex != acb->wqbuf_getIndex) { |
| arcmsr_write_ioctldata2iop(acb); |
| spin_unlock_irqrestore(&acb->wqbuffer_lock, flags); |
| return 0; /*need retry*/ |
| } else { |
| pQbuffer = &acb->wqbuffer[acb->wqbuf_putIndex]; |
| cnt2end = ARCMSR_MAX_QBUFFER - acb->wqbuf_putIndex; |
| if (user_len > cnt2end) { |
| memcpy(pQbuffer, ptmpuserbuffer, cnt2end); |
| ptmpuserbuffer += cnt2end; |
| user_len -= cnt2end; |
| acb->wqbuf_putIndex = 0; |
| pQbuffer = acb->wqbuffer; |
| } |
| memcpy(pQbuffer, ptmpuserbuffer, user_len); |
| acb->wqbuf_putIndex += user_len; |
| acb->wqbuf_putIndex %= ARCMSR_MAX_QBUFFER; |
| if (acb->acb_flags & ACB_F_MESSAGE_WQBUFFER_CLEARED) { |
| acb->acb_flags &= |
| ~ACB_F_MESSAGE_WQBUFFER_CLEARED; |
| arcmsr_write_ioctldata2iop(acb); |
| } |
| spin_unlock_irqrestore(&acb->wqbuffer_lock, flags); |
| return count; |
| } |
| } |
| |
| static ssize_t arcmsr_sysfs_iop_message_clear(struct file *filp, |
| struct kobject *kobj, |
| struct bin_attribute *bin, |
| char *buf, loff_t off, |
| size_t count) |
| { |
| struct device *dev = container_of(kobj,struct device,kobj); |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = (struct AdapterControlBlock *) host->hostdata; |
| uint8_t *pQbuffer; |
| unsigned long flags; |
| |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EACCES; |
| |
| arcmsr_clear_iop2drv_rqueue_buffer(acb); |
| acb->acb_flags |= |
| (ACB_F_MESSAGE_WQBUFFER_CLEARED |
| | ACB_F_MESSAGE_RQBUFFER_CLEARED |
| | ACB_F_MESSAGE_WQBUFFER_READED); |
| spin_lock_irqsave(&acb->rqbuffer_lock, flags); |
| acb->rqbuf_getIndex = 0; |
| acb->rqbuf_putIndex = 0; |
| spin_unlock_irqrestore(&acb->rqbuffer_lock, flags); |
| spin_lock_irqsave(&acb->wqbuffer_lock, flags); |
| acb->wqbuf_getIndex = 0; |
| acb->wqbuf_putIndex = 0; |
| spin_unlock_irqrestore(&acb->wqbuffer_lock, flags); |
| pQbuffer = acb->rqbuffer; |
| memset(pQbuffer, 0, sizeof (struct QBUFFER)); |
| pQbuffer = acb->wqbuffer; |
| memset(pQbuffer, 0, sizeof (struct QBUFFER)); |
| return 1; |
| } |
| |
| static const struct bin_attribute arcmsr_sysfs_message_read_attr = { |
| .attr = { |
| .name = "mu_read", |
| .mode = S_IRUSR , |
| }, |
| .size = ARCMSR_API_DATA_BUFLEN, |
| .read = arcmsr_sysfs_iop_message_read, |
| }; |
| |
| static const struct bin_attribute arcmsr_sysfs_message_write_attr = { |
| .attr = { |
| .name = "mu_write", |
| .mode = S_IWUSR, |
| }, |
| .size = ARCMSR_API_DATA_BUFLEN, |
| .write = arcmsr_sysfs_iop_message_write, |
| }; |
| |
| static const struct bin_attribute arcmsr_sysfs_message_clear_attr = { |
| .attr = { |
| .name = "mu_clear", |
| .mode = S_IWUSR, |
| }, |
| .size = 1, |
| .write = arcmsr_sysfs_iop_message_clear, |
| }; |
| |
| int arcmsr_alloc_sysfs_attr(struct AdapterControlBlock *acb) |
| { |
| struct Scsi_Host *host = acb->host; |
| int error; |
| |
| error = sysfs_create_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_read_attr); |
| if (error) { |
| printk(KERN_ERR "arcmsr: alloc sysfs mu_read failed\n"); |
| goto error_bin_file_message_read; |
| } |
| error = sysfs_create_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_write_attr); |
| if (error) { |
| printk(KERN_ERR "arcmsr: alloc sysfs mu_write failed\n"); |
| goto error_bin_file_message_write; |
| } |
| error = sysfs_create_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_clear_attr); |
| if (error) { |
| printk(KERN_ERR "arcmsr: alloc sysfs mu_clear failed\n"); |
| goto error_bin_file_message_clear; |
| } |
| return 0; |
| error_bin_file_message_clear: |
| sysfs_remove_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_write_attr); |
| error_bin_file_message_write: |
| sysfs_remove_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_read_attr); |
| error_bin_file_message_read: |
| return error; |
| } |
| |
| void arcmsr_free_sysfs_attr(struct AdapterControlBlock *acb) |
| { |
| struct Scsi_Host *host = acb->host; |
| |
| sysfs_remove_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_clear_attr); |
| sysfs_remove_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_write_attr); |
| sysfs_remove_bin_file(&host->shost_dev.kobj, &arcmsr_sysfs_message_read_attr); |
| } |
| |
| |
| static ssize_t |
| arcmsr_attr_host_driver_version(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, |
| "%s\n", |
| ARCMSR_DRIVER_VERSION); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_driver_posted_cmd(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| atomic_read(&acb->ccboutstandingcount)); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_driver_reset(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| acb->num_resets); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_driver_abort(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| acb->num_aborts); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_fw_model(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| return snprintf(buf, PAGE_SIZE, |
| "%s\n", |
| acb->firm_model); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_fw_version(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| |
| return snprintf(buf, PAGE_SIZE, |
| "%s\n", |
| acb->firm_version); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_fw_request_len(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| acb->firm_request_len); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_fw_numbers_queue(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| acb->firm_numbers_queue); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_fw_sdram_size(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| acb->firm_sdram_size); |
| } |
| |
| static ssize_t |
| arcmsr_attr_host_fw_hd_channels(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct Scsi_Host *host = class_to_shost(dev); |
| struct AdapterControlBlock *acb = |
| (struct AdapterControlBlock *) host->hostdata; |
| |
| return snprintf(buf, PAGE_SIZE, |
| "%4d\n", |
| acb->firm_hd_channels); |
| } |
| |
| static DEVICE_ATTR(host_driver_version, S_IRUGO, arcmsr_attr_host_driver_version, NULL); |
| static DEVICE_ATTR(host_driver_posted_cmd, S_IRUGO, arcmsr_attr_host_driver_posted_cmd, NULL); |
| static DEVICE_ATTR(host_driver_reset, S_IRUGO, arcmsr_attr_host_driver_reset, NULL); |
| static DEVICE_ATTR(host_driver_abort, S_IRUGO, arcmsr_attr_host_driver_abort, NULL); |
| static DEVICE_ATTR(host_fw_model, S_IRUGO, arcmsr_attr_host_fw_model, NULL); |
| static DEVICE_ATTR(host_fw_version, S_IRUGO, arcmsr_attr_host_fw_version, NULL); |
| static DEVICE_ATTR(host_fw_request_len, S_IRUGO, arcmsr_attr_host_fw_request_len, NULL); |
| static DEVICE_ATTR(host_fw_numbers_queue, S_IRUGO, arcmsr_attr_host_fw_numbers_queue, NULL); |
| static DEVICE_ATTR(host_fw_sdram_size, S_IRUGO, arcmsr_attr_host_fw_sdram_size, NULL); |
| static DEVICE_ATTR(host_fw_hd_channels, S_IRUGO, arcmsr_attr_host_fw_hd_channels, NULL); |
| |
| static struct attribute *arcmsr_host_attrs[] = { |
| &dev_attr_host_driver_version.attr, |
| &dev_attr_host_driver_posted_cmd.attr, |
| &dev_attr_host_driver_reset.attr, |
| &dev_attr_host_driver_abort.attr, |
| &dev_attr_host_fw_model.attr, |
| &dev_attr_host_fw_version.attr, |
| &dev_attr_host_fw_request_len.attr, |
| &dev_attr_host_fw_numbers_queue.attr, |
| &dev_attr_host_fw_sdram_size.attr, |
| &dev_attr_host_fw_hd_channels.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group arcmsr_host_attr_group = { |
| .attrs = arcmsr_host_attrs, |
| }; |
| |
| const struct attribute_group *arcmsr_host_groups[] = { |
| &arcmsr_host_attr_group, |
| NULL |
| }; |