/* * Huawei SSD device driver * Copyright (c) 2016, Huawei Technologies Co., Ltd. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. */ #ifndef LINUX_VERSION_CODE #include #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* HDIO_GETGEO */ #include #include #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)) #include #endif #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,2,0)) #include #include #else #include #endif #include #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,17)) #include #endif /* driver */ #define MODULE_NAME "hio" #define DRIVER_VERSION "2.1.0.23" #define DRIVER_VERSION_LEN 16 #define SSD_FW_MIN 0x1 #define SSD_DEV_NAME MODULE_NAME #define SSD_DEV_NAME_LEN 16 #define SSD_CDEV_NAME "c"SSD_DEV_NAME #define SSD_SDEV_NAME "s"SSD_DEV_NAME #define SSD_CMAJOR 0 #define SSD_MAJOR 0 #define SSD_MAJOR_SL 0 #define SSD_MINORS 16 #define SSD_MAX_DEV 702 #define SSD_ALPHABET_NUM 26 #define hio_info(f, arg...) printk(KERN_INFO MODULE_NAME"info: " f , ## arg) #define hio_note(f, arg...) printk(KERN_NOTICE MODULE_NAME"note: " f , ## arg) #define hio_warn(f, arg...) printk(KERN_WARNING MODULE_NAME"warn: " f , ## arg) #define hio_err(f, arg...) printk(KERN_ERR MODULE_NAME"err: " f , ## arg) /* slave port */ #define SSD_SLAVE_PORT_DEVID 0x000a /* int mode */ /* 2.6.9 msi affinity bug, should turn msi & msi-x off */ //#define SSD_MSI #define SSD_ESCAPE_IRQ //#define SSD_MSIX #ifndef MODULE #define SSD_MSIX #endif #define SSD_MSIX_VEC 8 #ifdef SSD_MSIX #undef SSD_MSI //#undef SSD_ESCAPE_IRQ #define SSD_MSIX_AFFINITY_FORCE #endif #define SSD_TRIM /* Over temperature protect */ #define SSD_OT_PROTECT #ifdef SSD_QUEUE_PBIO #define BIO_SSD_PBIO 20 #endif /* debug */ //#define SSD_DEBUG_ERR /* cmd timer */ #define SSD_CMD_TIMEOUT (60*HZ) /* i2c & smbus */ #define SSD_SPI_TIMEOUT (5*HZ) #define SSD_I2C_TIMEOUT (5*HZ) #define SSD_I2C_MAX_DATA (127) #define SSD_SMBUS_BLOCK_MAX (32) #define SSD_SMBUS_DATA_MAX (SSD_SMBUS_BLOCK_MAX + 2) /* wait for init */ #define SSD_INIT_WAIT (1000) //1s #define SSD_CONTROLLER_WAIT (20*1000/SSD_INIT_WAIT) //20s #define SSD_INIT_MAX_WAIT (500*1000/SSD_INIT_WAIT) //500s #define SSD_INIT_MAX_WAIT_V3_2 (1400*1000/SSD_INIT_WAIT) //1400s #define SSD_RAM_INIT_MAX_WAIT (10*1000/SSD_INIT_WAIT) //10s #define SSD_CH_INFO_MAX_WAIT (10*1000/SSD_INIT_WAIT) //10s /* blkdev busy wait */ #define SSD_DEV_BUSY_WAIT 1000 //ms #define SSD_DEV_BUSY_MAX_WAIT (8*1000/SSD_DEV_BUSY_WAIT) //8s /* smbus retry */ #define SSD_SMBUS_RETRY_INTERVAL (5) //ms #define SSD_SMBUS_RETRY_MAX (1000/SSD_SMBUS_RETRY_INTERVAL) #define SSD_BM_RETRY_MAX 7 /* bm routine interval */ #define SSD_BM_CAP_LEARNING_DELAY (10*60*1000) /* routine interval */ #define SSD_ROUTINE_INTERVAL (10*1000) //10s #define SSD_HWMON_ROUTINE_TICK (60*1000/SSD_ROUTINE_INTERVAL) #define SSD_CAPMON_ROUTINE_TICK ((3600*1000/SSD_ROUTINE_INTERVAL)*24*30) #define SSD_CAPMON2_ROUTINE_TICK (10*60*1000/SSD_ROUTINE_INTERVAL) //fault recover /* dma align */ #define SSD_DMA_ALIGN (16) /* some hw defalut */ #define SSD_LOG_MAX_SZ 4096 #define SSD_NAND_OOB_SZ 1024 #define SSD_NAND_ID_SZ 8 #define SSD_NAND_ID_BUFF_SZ 1024 #define SSD_NAND_MAX_CE 2 #define SSD_BBT_RESERVED 8 #define SSD_ECC_MAX_FLIP (64+1) #define SSD_RAM_ALIGN 16 #define SSD_RELOAD_FLAG 0x3333CCCC #define SSD_RELOAD_FW 0xAA5555AA #define SSD_RESET_NOINIT 0xAA5555AA #define SSD_RESET 0x55AAAA55 #define SSD_RESET_FULL 0x5A //#define SSD_RESET_WAIT 1000 //1s //#define SSD_RESET_MAX_WAIT (200*1000/SSD_RESET_WAIT) //200s /* reverion 1 */ #define SSD_PROTOCOL_V1 0x0 #define SSD_ROM_SIZE (16*1024*1024) #define SSD_ROM_BLK_SIZE (256*1024) #define SSD_ROM_PAGE_SIZE (256) #define SSD_ROM_NR_BRIDGE_FW 2 #define SSD_ROM_NR_CTRL_FW 2 #define SSD_ROM_BRIDGE_FW_BASE 0 #define SSD_ROM_BRIDGE_FW_SIZE (2*1024*1024) #define SSD_ROM_CTRL_FW_BASE (SSD_ROM_NR_BRIDGE_FW*SSD_ROM_BRIDGE_FW_SIZE) #define SSD_ROM_CTRL_FW_SIZE (5*1024*1024) #define SSD_ROM_LABEL_BASE (SSD_ROM_CTRL_FW_BASE+SSD_ROM_CTRL_FW_SIZE*SSD_ROM_NR_CTRL_FW) #define SSD_ROM_VP_BASE (SSD_ROM_LABEL_BASE+SSD_ROM_BLK_SIZE) /* reverion 3 */ #define SSD_PROTOCOL_V3 0x3000000 #define SSD_PROTOCOL_V3_1_1 0x3010001 #define SSD_PROTOCOL_V3_1_3 0x3010003 #define SSD_PROTOCOL_V3_2 0x3020000 #define SSD_PROTOCOL_V3_2_1 0x3020001 /* <4KB improved */ #define SSD_PROTOCOL_V3_2_2 0x3020002 /* ot protect */ #define SSD_PROTOCOL_V3_2_4 0x3020004 #define SSD_PV3_ROM_NR_BM_FW 1 #define SSD_PV3_ROM_BM_FW_SZ (64*1024*8) #define SSD_ROM_LOG_SZ (64*1024*4) #define SSD_ROM_NR_SMART_MAX 2 #define SSD_PV3_ROM_NR_SMART SSD_ROM_NR_SMART_MAX #define SSD_PV3_ROM_SMART_SZ (64*1024) /* reverion 3.2 */ #define SSD_PV3_2_ROM_LOG_SZ (64*1024*80) /* 5MB */ #define SSD_PV3_2_ROM_SEC_SZ (256*1024) /* 256KB */ /* register */ #define SSD_REQ_FIFO_REG 0x0000 #define SSD_RESP_FIFO_REG 0x0008 //0x0010 #define SSD_RESP_PTR_REG 0x0010 //0x0018 #define SSD_INTR_INTERVAL_REG 0x0018 #define SSD_READY_REG 0x001C #define SSD_BRIDGE_TEST_REG 0x0020 #define SSD_STRIPE_SIZE_REG 0x0028 #define SSD_CTRL_VER_REG 0x0030 //controller #define SSD_BRIDGE_VER_REG 0x0034 //bridge #define SSD_PCB_VER_REG 0x0038 #define SSD_BURN_FLAG_REG 0x0040 #define SSD_BRIDGE_INFO_REG 0x0044 #define SSD_WL_VAL_REG 0x0048 //32-bit #define SSD_BB_INFO_REG 0x004C #define SSD_ECC_TEST_REG 0x0050 //test only #define SSD_ERASE_TEST_REG 0x0058 //test only #define SSD_WRITE_TEST_REG 0x0060 //test only #define SSD_RESET_REG 0x0068 #define SSD_RELOAD_FW_REG 0x0070 #define SSD_RESERVED_BLKS_REG 0x0074 #define SSD_VALID_PAGES_REG 0x0078 #define SSD_CH_INFO_REG 0x007C #define SSD_CTRL_TEST_REG_SZ 0x8 #define SSD_CTRL_TEST_REG0 0x0080 #define SSD_CTRL_TEST_REG1 0x0088 #define SSD_CTRL_TEST_REG2 0x0090 #define SSD_CTRL_TEST_REG3 0x0098 #define SSD_CTRL_TEST_REG4 0x00A0 #define SSD_CTRL_TEST_REG5 0x00A8 #define SSD_CTRL_TEST_REG6 0x00B0 #define SSD_CTRL_TEST_REG7 0x00B8 #define SSD_FLASH_INFO_REG0 0x00C0 #define SSD_FLASH_INFO_REG1 0x00C8 #define SSD_FLASH_INFO_REG2 0x00D0 #define SSD_FLASH_INFO_REG3 0x00D8 #define SSD_FLASH_INFO_REG4 0x00E0 #define SSD_FLASH_INFO_REG5 0x00E8 #define SSD_FLASH_INFO_REG6 0x00F0 #define SSD_FLASH_INFO_REG7 0x00F8 #define SSD_RESP_INFO_REG 0x01B8 #define SSD_NAND_BUFF_BASE 0x01BC //for nand write #define SSD_CHIP_INFO_REG_SZ 0x10 #define SSD_CHIP_INFO_REG0 0x0100 //128 bit #define SSD_CHIP_INFO_REG1 0x0110 #define SSD_CHIP_INFO_REG2 0x0120 #define SSD_CHIP_INFO_REG3 0x0130 #define SSD_CHIP_INFO_REG4 0x0140 #define SSD_CHIP_INFO_REG5 0x0150 #define SSD_CHIP_INFO_REG6 0x0160 #define SSD_CHIP_INFO_REG7 0x0170 #define SSD_RAM_INFO_REG 0x01C4 #define SSD_BBT_BASE_REG 0x01C8 #define SSD_ECT_BASE_REG 0x01CC #define SSD_CLEAR_INTR_REG 0x01F0 #define SSD_INIT_STATE_REG_SZ 0x8 #define SSD_INIT_STATE_REG0 0x0200 #define SSD_INIT_STATE_REG1 0x0208 #define SSD_INIT_STATE_REG2 0x0210 #define SSD_INIT_STATE_REG3 0x0218 #define SSD_INIT_STATE_REG4 0x0220 #define SSD_INIT_STATE_REG5 0x0228 #define SSD_INIT_STATE_REG6 0x0230 #define SSD_INIT_STATE_REG7 0x0238 #define SSD_ROM_INFO_REG 0x0600 #define SSD_ROM_BRIDGE_FW_INFO_REG 0x0604 #define SSD_ROM_CTRL_FW_INFO_REG 0x0608 #define SSD_ROM_VP_INFO_REG 0x060C #define SSD_LOG_INFO_REG 0x0610 #define SSD_LED_REG 0x0614 #define SSD_MSG_BASE_REG 0x06F8 /*spi reg */ #define SSD_SPI_REG_CMD 0x0180 #define SSD_SPI_REG_CMD_HI 0x0184 #define SSD_SPI_REG_WDATA 0x0188 #define SSD_SPI_REG_ID 0x0190 #define SSD_SPI_REG_STATUS 0x0198 #define SSD_SPI_REG_RDATA 0x01A0 #define SSD_SPI_REG_READY 0x01A8 /* i2c register */ #define SSD_I2C_CTRL_REG 0x06F0 #define SSD_I2C_RDATA_REG 0x06F4 /* temperature reg */ #define SSD_BRIGE_TEMP_REG 0x0618 #define SSD_CTRL_TEMP_REG0 0x0700 #define SSD_CTRL_TEMP_REG1 0x0708 #define SSD_CTRL_TEMP_REG2 0x0710 #define SSD_CTRL_TEMP_REG3 0x0718 #define SSD_CTRL_TEMP_REG4 0x0720 #define SSD_CTRL_TEMP_REG5 0x0728 #define SSD_CTRL_TEMP_REG6 0x0730 #define SSD_CTRL_TEMP_REG7 0x0738 /* reversion 3 reg */ #define SSD_PROTOCOL_VER_REG 0x01B4 #define SSD_FLUSH_TIMEOUT_REG 0x02A4 #define SSD_BM_FAULT_REG 0x0660 #define SSD_PV3_RAM_STATUS_REG_SZ 0x4 #define SSD_PV3_RAM_STATUS_REG0 0x0260 #define SSD_PV3_RAM_STATUS_REG1 0x0264 #define SSD_PV3_RAM_STATUS_REG2 0x0268 #define SSD_PV3_RAM_STATUS_REG3 0x026C #define SSD_PV3_RAM_STATUS_REG4 0x0270 #define SSD_PV3_RAM_STATUS_REG5 0x0274 #define SSD_PV3_RAM_STATUS_REG6 0x0278 #define SSD_PV3_RAM_STATUS_REG7 0x027C #define SSD_PV3_CHIP_INFO_REG_SZ 0x40 #define SSD_PV3_CHIP_INFO_REG0 0x0300 #define SSD_PV3_CHIP_INFO_REG1 0x0340 #define SSD_PV3_CHIP_INFO_REG2 0x0380 #define SSD_PV3_CHIP_INFO_REG3 0x03B0 #define SSD_PV3_CHIP_INFO_REG4 0x0400 #define SSD_PV3_CHIP_INFO_REG5 0x0440 #define SSD_PV3_CHIP_INFO_REG6 0x0480 #define SSD_PV3_CHIP_INFO_REG7 0x04B0 #define SSD_PV3_INIT_STATE_REG_SZ 0x20 #define SSD_PV3_INIT_STATE_REG0 0x0500 #define SSD_PV3_INIT_STATE_REG1 0x0520 #define SSD_PV3_INIT_STATE_REG2 0x0540 #define SSD_PV3_INIT_STATE_REG3 0x0560 #define SSD_PV3_INIT_STATE_REG4 0x0580 #define SSD_PV3_INIT_STATE_REG5 0x05A0 #define SSD_PV3_INIT_STATE_REG6 0x05C0 #define SSD_PV3_INIT_STATE_REG7 0x05E0 /* reversion 3.1.1 reg */ #define SSD_FULL_RESET_REG 0x01B0 #define SSD_CTRL_REG_ZONE_SZ 0x800 #define SSD_BB_THRESHOLD_L1_REG 0x2C0 #define SSD_BB_THRESHOLD_L2_REG 0x2C4 #define SSD_BB_ACC_REG_SZ 0x4 #define SSD_BB_ACC_REG0 0x21C0 #define SSD_BB_ACC_REG1 0x29C0 #define SSD_BB_ACC_REG2 0x31C0 #define SSD_EC_THRESHOLD_L1_REG 0x2C8 #define SSD_EC_THRESHOLD_L2_REG 0x2CC #define SSD_EC_ACC_REG_SZ 0x4 #define SSD_EC_ACC_REG0 0x21E0 #define SSD_EC_ACC_REG1 0x29E0 #define SSD_EC_ACC_REG2 0x31E0 /* reversion 3.1.2 & 3.1.3 reg */ #define SSD_HW_STATUS_REG 0x02AC #define SSD_PLP_INFO_REG 0x0664 /*reversion 3.2 reg*/ #define SSD_POWER_ON_REG 0x01EC #define SSD_PCIE_LINKSTATUS_REG 0x01F8 #define SSD_PL_CAP_LEARN_REG 0x01FC #define SSD_FPGA_1V0_REG0 0x2070 #define SSD_FPGA_1V8_REG0 0x2078 #define SSD_FPGA_1V0_REG1 0x2870 #define SSD_FPGA_1V8_REG1 0x2878 /*reversion 3.2 reg*/ #define SSD_READ_OT_REG0 0x2260 #define SSD_WRITE_OT_REG0 0x2264 #define SSD_READ_OT_REG1 0x2A60 #define SSD_WRITE_OT_REG1 0x2A64 /* function */ #define SSD_FUNC_READ 0x01 #define SSD_FUNC_WRITE 0x02 #define SSD_FUNC_NAND_READ_WOOB 0x03 #define SSD_FUNC_NAND_READ 0x04 #define SSD_FUNC_NAND_WRITE 0x05 #define SSD_FUNC_NAND_ERASE 0x06 #define SSD_FUNC_NAND_READ_ID 0x07 #define SSD_FUNC_READ_LOG 0x08 #define SSD_FUNC_TRIM 0x09 #define SSD_FUNC_RAM_READ 0x10 #define SSD_FUNC_RAM_WRITE 0x11 #define SSD_FUNC_FLUSH 0x12 //cache / bbt /* spi function */ #define SSD_SPI_CMD_PROGRAM 0x02 #define SSD_SPI_CMD_READ 0x03 #define SSD_SPI_CMD_W_DISABLE 0x04 #define SSD_SPI_CMD_READ_STATUS 0x05 #define SSD_SPI_CMD_W_ENABLE 0x06 #define SSD_SPI_CMD_ERASE 0xd8 #define SSD_SPI_CMD_CLSR 0x30 #define SSD_SPI_CMD_READ_ID 0x9f /* i2c */ #define SSD_I2C_CTRL_READ 0x00 #define SSD_I2C_CTRL_WRITE 0x01 /* i2c internal register */ #define SSD_I2C_CFG_REG 0x00 #define SSD_I2C_DATA_REG 0x01 #define SSD_I2C_CMD_REG 0x02 #define SSD_I2C_STATUS_REG 0x03 #define SSD_I2C_SADDR_REG 0x04 #define SSD_I2C_LEN_REG 0x05 #define SSD_I2C_RLEN_REG 0x06 #define SSD_I2C_WLEN_REG 0x07 #define SSD_I2C_RESET_REG 0x08 //write for reset #define SSD_I2C_PRER_REG 0x09 /* hw mon */ /* FPGA volt = ADC_value / 4096 * 3v */ #define SSD_FPGA_1V0_ADC_MIN 1228 // 0.9v #define SSD_FPGA_1V0_ADC_MAX 1502 // 1.1v #define SSD_FPGA_1V8_ADC_MIN 2211 // 1.62v #define SSD_FPGA_1V8_ADC_MAX 2703 // 1.98 /* ADC value */ #define SSD_FPGA_VOLT_MAX(val) (((val) & 0xffff) >> 4) #define SSD_FPGA_VOLT_MIN(val) (((val >> 16) & 0xffff) >> 4) #define SSD_FPGA_VOLT_CUR(val) (((val >> 32) & 0xffff) >> 4) #define SSD_FPGA_VOLT(val) ((val * 3000) >> 12) #define SSD_VOLT_LOG_DATA(idx, ctrl, volt) (((uint32_t)idx << 24) | ((uint32_t)ctrl << 16) | ((uint32_t)volt)) enum ssd_fpga_volt { SSD_FPGA_1V0 = 0, SSD_FPGA_1V8, SSD_FPGA_VOLT_NR }; enum ssd_clock { SSD_CLOCK_166M_LOST = 0, SSD_CLOCK_166M_SKEW, SSD_CLOCK_156M_LOST, SSD_CLOCK_156M_SKEW, SSD_CLOCK_NR }; /* sensor */ #define SSD_SENSOR_LM75_SADDRESS (0x49 << 1) #define SSD_SENSOR_LM80_SADDRESS (0x28 << 1) #define SSD_SENSOR_CONVERT_TEMP(val) ((int)(val >> 8)) #define SSD_INLET_OT_TEMP (55) //55 DegC #define SSD_INLET_OT_HYST (50) //50 DegC #define SSD_FLASH_OT_TEMP (70) //70 DegC #define SSD_FLASH_OT_HYST (65) //65 DegC enum ssd_sensor { SSD_SENSOR_LM80 = 0, SSD_SENSOR_LM75, SSD_SENSOR_NR }; /* lm75 */ enum ssd_lm75_reg { SSD_LM75_REG_TEMP = 0, SSD_LM75_REG_CONF, SSD_LM75_REG_THYST, SSD_LM75_REG_TOS }; /* lm96080 */ #define SSD_LM80_REG_IN_MAX(nr) (0x2a + (nr) * 2) #define SSD_LM80_REG_IN_MIN(nr) (0x2b + (nr) * 2) #define SSD_LM80_REG_IN(nr) (0x20 + (nr)) #define SSD_LM80_REG_FAN1 0x28 #define SSD_LM80_REG_FAN2 0x29 #define SSD_LM80_REG_FAN_MIN(nr) (0x3b + (nr)) #define SSD_LM80_REG_TEMP 0x27 #define SSD_LM80_REG_TEMP_HOT_MAX 0x38 #define SSD_LM80_REG_TEMP_HOT_HYST 0x39 #define SSD_LM80_REG_TEMP_OS_MAX 0x3a #define SSD_LM80_REG_TEMP_OS_HYST 0x3b #define SSD_LM80_REG_CONFIG 0x00 #define SSD_LM80_REG_ALARM1 0x01 #define SSD_LM80_REG_ALARM2 0x02 #define SSD_LM80_REG_MASK1 0x03 #define SSD_LM80_REG_MASK2 0x04 #define SSD_LM80_REG_FANDIV 0x05 #define SSD_LM80_REG_RES 0x06 #define SSD_LM80_CONVERT_VOLT(val) ((val * 10) >> 8) #define SSD_LM80_3V3_VOLT(val) ((val)*33/19) #define SSD_LM80_CONV_INTERVAL (1000) enum ssd_lm80_in { SSD_LM80_IN_CAP = 0, SSD_LM80_IN_1V2, SSD_LM80_IN_1V2a, SSD_LM80_IN_1V5, SSD_LM80_IN_1V8, SSD_LM80_IN_FPGA_3V3, SSD_LM80_IN_3V3, SSD_LM80_IN_NR }; struct ssd_lm80_limit { uint8_t low; uint8_t high; }; /* +/- 5% except cap in*/ static struct ssd_lm80_limit ssd_lm80_limit[SSD_LM80_IN_NR] = { {171, 217}, /* CAP in: 1710 ~ 2170 */ {114, 126}, {114, 126}, {142, 158}, {171, 189}, {180, 200}, {180, 200}, }; /* temperature sensors */ enum ssd_temp_sensor { SSD_TEMP_INLET = 0, SSD_TEMP_FLASH, SSD_TEMP_CTRL, SSD_TEMP_NR }; #ifdef SSD_OT_PROTECT #define SSD_OT_DELAY (60) //ms #define SSD_OT_TEMP (90) //90 DegC #define SSD_OT_TEMP_HYST (85) //85 DegC #endif /* fpga temperature */ //#define CONVERT_TEMP(val) ((float)(val)*503.975f/4096.0f-273.15f) #define CONVERT_TEMP(val) ((val)*504/4096-273) #define MAX_TEMP(val) CONVERT_TEMP(((val & 0xffff) >> 4)) #define MIN_TEMP(val) CONVERT_TEMP((((val>>16) & 0xffff) >> 4)) #define CUR_TEMP(val) CONVERT_TEMP((((val>>32) & 0xffff) >> 4)) /* CAP monitor */ #define SSD_PL_CAP_U1 SSD_LM80_REG_IN(SSD_LM80_IN_CAP) #define SSD_PL_CAP_U2 SSD_LM80_REG_IN(SSD_LM80_IN_1V8) #define SSD_PL_CAP_LEARN(u1, u2, t) ((t*(u1+u2))/(2*162*(u1-u2))) #define SSD_PL_CAP_LEARN_WAIT (20) //20ms #define SSD_PL_CAP_LEARN_MAX_WAIT (1000/SSD_PL_CAP_LEARN_WAIT) //1s #define SSD_PL_CAP_CHARGE_WAIT (1000) #define SSD_PL_CAP_CHARGE_MAX_WAIT ((120*1000)/SSD_PL_CAP_CHARGE_WAIT) //120s #define SSD_PL_CAP_VOLT(val) (val*7) #define SSD_PL_CAP_VOLT_FULL (13700) #define SSD_PL_CAP_VOLT_READY (12880) #define SSD_PL_CAP_THRESHOLD (8900) #define SSD_PL_CAP_CP_THRESHOLD (5800) #define SSD_PL_CAP_THRESHOLD_HYST (100) enum ssd_pl_cap_status { SSD_PL_CAP = 0, SSD_PL_CAP_NR }; enum ssd_pl_cap_type { SSD_PL_CAP_DEFAULT = 0, /* 4 cap */ SSD_PL_CAP_CP /* 3 cap */ }; /* hwmon offset */ #define SSD_HWMON_OFFS_TEMP (0) #define SSD_HWMON_OFFS_SENSOR (SSD_HWMON_OFFS_TEMP + SSD_TEMP_NR) #define SSD_HWMON_OFFS_PL_CAP (SSD_HWMON_OFFS_SENSOR + SSD_SENSOR_NR) #define SSD_HWMON_OFFS_LM80 (SSD_HWMON_OFFS_PL_CAP + SSD_PL_CAP_NR) #define SSD_HWMON_OFFS_CLOCK (SSD_HWMON_OFFS_LM80 + SSD_LM80_IN_NR) #define SSD_HWMON_OFFS_FPGA (SSD_HWMON_OFFS_CLOCK + SSD_CLOCK_NR) #define SSD_HWMON_TEMP(idx) (SSD_HWMON_OFFS_TEMP + idx) #define SSD_HWMON_SENSOR(idx) (SSD_HWMON_OFFS_SENSOR + idx) #define SSD_HWMON_PL_CAP(idx) (SSD_HWMON_OFFS_PL_CAP + idx) #define SSD_HWMON_LM80(idx) (SSD_HWMON_OFFS_LM80 + idx) #define SSD_HWMON_CLOCK(idx) (SSD_HWMON_OFFS_CLOCK + idx) #define SSD_HWMON_FPGA(ctrl, idx) (SSD_HWMON_OFFS_FPGA + (ctrl * SSD_FPGA_VOLT_NR) + idx) /* fifo */ typedef struct sfifo { uint32_t in; uint32_t out; uint32_t size; uint32_t esize; uint32_t mask; spinlock_t lock; void *data; } sfifo_t; static int sfifo_alloc(struct sfifo *fifo, uint32_t size, uint32_t esize) { uint32_t __size = 1; if (!fifo || size > INT_MAX || esize == 0) { return -EINVAL; } while (__size < size) __size <<= 1; if (__size < 2) { return -EINVAL; } fifo->data = vmalloc(esize * __size); if (!fifo->data) { return -ENOMEM; } fifo->in = 0; fifo->out = 0; fifo->mask = __size - 1; fifo->size = __size; fifo->esize = esize; spin_lock_init(&fifo->lock); return 0; } static void sfifo_free(struct sfifo *fifo) { if (!fifo) { return; } vfree(fifo->data); fifo->data = NULL; fifo->in = 0; fifo->out = 0; fifo->mask = 0; fifo->size = 0; fifo->esize = 0; } static int __sfifo_put(struct sfifo *fifo, void *val) { if (((fifo->in + 1) & fifo->mask) == fifo->out) { return -1; } memcpy((fifo->data + (fifo->in * fifo->esize)), val, fifo->esize); fifo->in = (fifo->in + 1) & fifo->mask; return 0; } static int sfifo_put(struct sfifo *fifo, void *val) { int ret = 0; if (!fifo || !val) { return -EINVAL; } if (!in_interrupt()) { spin_lock_irq(&fifo->lock); ret = __sfifo_put(fifo, val); spin_unlock_irq(&fifo->lock); } else { spin_lock(&fifo->lock); ret = __sfifo_put(fifo, val); spin_unlock(&fifo->lock); } return ret; } static int __sfifo_get(struct sfifo *fifo, void *val) { if (fifo->out == fifo->in) { return -1; } memcpy(val, (fifo->data + (fifo->out * fifo->esize)), fifo->esize); fifo->out = (fifo->out + 1) & fifo->mask; return 0; } static int sfifo_get(struct sfifo *fifo, void *val) { int ret = 0; if (!fifo || !val) { return -EINVAL; } if (!in_interrupt()) { spin_lock_irq(&fifo->lock); ret = __sfifo_get(fifo, val); spin_unlock_irq(&fifo->lock); } else { spin_lock(&fifo->lock); ret = __sfifo_get(fifo, val); spin_unlock(&fifo->lock); } return ret; } /* bio list */ #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) struct ssd_blist { struct bio *prev; struct bio *next; }; static inline void ssd_blist_init(struct ssd_blist *ssd_bl) { ssd_bl->prev = NULL; ssd_bl->next = NULL; } static inline struct bio *ssd_blist_get(struct ssd_blist *ssd_bl) { struct bio *bio = ssd_bl->prev; ssd_bl->prev = NULL; ssd_bl->next = NULL; return bio; } static inline void ssd_blist_add(struct ssd_blist *ssd_bl, struct bio *bio) { bio->bi_next = NULL; if (ssd_bl->next) { ssd_bl->next->bi_next = bio; } else { ssd_bl->prev = bio; } ssd_bl->next = bio; } #else #define ssd_blist bio_list #define ssd_blist_init bio_list_init #define ssd_blist_get bio_list_get #define ssd_blist_add bio_list_add #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)) #define bio_start(bio) (bio->bi_sector) #else #define bio_start(bio) (bio->bi_iter.bi_sector) #endif /* mutex */ #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)) #define mutex_lock down #define mutex_unlock up #define mutex semaphore #define mutex_init init_MUTEX #endif /* i2c */ typedef union ssd_i2c_ctrl { uint32_t val; struct { uint8_t wdata; uint8_t addr; uint16_t rw:1; uint16_t pad:15; } bits; }__attribute__((packed)) ssd_i2c_ctrl_t; typedef union ssd_i2c_data { uint32_t val; struct { uint32_t rdata:8; uint32_t valid:1; uint32_t pad:23; } bits; }__attribute__((packed)) ssd_i2c_data_t; /* write mode */ enum ssd_write_mode { SSD_WMODE_BUFFER = 0, SSD_WMODE_BUFFER_EX, SSD_WMODE_FUA, /* dummy */ SSD_WMODE_AUTO, SSD_WMODE_DEFAULT }; /* reset type */ enum ssd_reset_type { SSD_RST_NOINIT = 0, SSD_RST_NORMAL, SSD_RST_FULL }; /* ssd msg */ typedef struct ssd_sg_entry { uint64_t block:48; uint64_t length:16; uint64_t buf; }__attribute__((packed))ssd_sg_entry_t; typedef struct ssd_rw_msg { uint8_t tag; uint8_t flag; uint8_t nsegs; uint8_t fun; uint32_t reserved; //for 64-bit align struct ssd_sg_entry sge[1]; //base }__attribute__((packed))ssd_rw_msg_t; typedef struct ssd_resp_msg { uint8_t tag; uint8_t status:2; uint8_t bitflip:6; uint8_t log; uint8_t fun; uint32_t reserved; }__attribute__((packed))ssd_resp_msg_t; typedef struct ssd_flush_msg { uint8_t tag; uint8_t flag:2; //flash cache 0 or bbt 1 uint8_t flash:6; uint8_t ctrl_idx; uint8_t fun; uint32_t reserved; //align }__attribute__((packed))ssd_flush_msg_t; typedef struct ssd_nand_op_msg { uint8_t tag; uint8_t flag; uint8_t ctrl_idx; uint8_t fun; uint32_t reserved; //align uint16_t page_count; uint8_t chip_ce; uint8_t chip_no; uint32_t page_no; uint64_t buf; }__attribute__((packed))ssd_nand_op_msg_t; typedef struct ssd_ram_op_msg { uint8_t tag; uint8_t flag; uint8_t ctrl_idx; uint8_t fun; uint32_t reserved; //align uint32_t start; uint32_t length; uint64_t buf; }__attribute__((packed))ssd_ram_op_msg_t; /* log msg */ typedef struct ssd_log_msg { uint8_t tag; uint8_t flag; uint8_t ctrl_idx; uint8_t fun; uint32_t reserved; //align uint64_t buf; }__attribute__((packed))ssd_log_msg_t; typedef struct ssd_log_op_msg { uint8_t tag; uint8_t flag; uint8_t ctrl_idx; uint8_t fun; uint32_t reserved; //align uint64_t reserved1; //align uint64_t buf; }__attribute__((packed))ssd_log_op_msg_t; typedef struct ssd_log_resp_msg { uint8_t tag; uint16_t status :2; uint16_t reserved1 :2; //align with the normal resp msg uint16_t nr_log :12; uint8_t fun; uint32_t reserved; }__attribute__((packed))ssd_log_resp_msg_t; /* resp msg */ typedef union ssd_response_msq { ssd_resp_msg_t resp_msg; ssd_log_resp_msg_t log_resp_msg; uint64_t u64_msg; uint32_t u32_msg[2]; } ssd_response_msq_t; /* custom struct */ typedef struct ssd_protocol_info { uint32_t ver; uint32_t init_state_reg; uint32_t init_state_reg_sz; uint32_t chip_info_reg; uint32_t chip_info_reg_sz; } ssd_protocol_info_t; typedef struct ssd_hw_info { uint32_t bridge_ver; uint32_t ctrl_ver; uint32_t cmd_fifo_sz; uint32_t cmd_fifo_sz_mask; uint32_t cmd_max_sg; uint32_t sg_max_sec; uint32_t resp_ptr_sz; uint32_t resp_msg_sz; uint16_t nr_ctrl; uint16_t nr_data_ch; uint16_t nr_ch; uint16_t max_ch; uint16_t nr_chip; uint8_t pcb_ver; uint8_t upper_pcb_ver; uint8_t nand_vendor_id; uint8_t nand_dev_id; uint8_t max_ce; uint8_t id_size; uint16_t oob_size; uint16_t bbf_pages; uint16_t bbf_seek; // uint16_t page_count; //per block uint32_t page_size; uint32_t block_count; //per flash uint64_t ram_size; uint32_t ram_align; uint32_t ram_max_len; uint64_t bbt_base; uint32_t bbt_size; uint64_t md_base; //metadata uint32_t md_size; uint32_t md_entry_sz; uint32_t log_sz; uint64_t nand_wbuff_base; uint32_t md_reserved_blks; uint32_t reserved_blks; uint32_t valid_pages; uint32_t max_valid_pages; uint64_t size; } ssd_hw_info_t; typedef struct ssd_hw_info_extend { uint8_t board_type; uint8_t cap_type; uint8_t plp_type; uint8_t work_mode; uint8_t form_factor; uint8_t pad[59]; }ssd_hw_info_extend_t; typedef struct ssd_rom_info { uint32_t size; uint32_t block_size; uint16_t page_size; uint8_t nr_bridge_fw; uint8_t nr_ctrl_fw; uint8_t nr_bm_fw; uint8_t nr_smart; uint32_t bridge_fw_base; uint32_t bridge_fw_sz; uint32_t ctrl_fw_base; uint32_t ctrl_fw_sz; uint32_t bm_fw_base; uint32_t bm_fw_sz; uint32_t log_base; uint32_t log_sz; uint32_t smart_base; uint32_t smart_sz; uint32_t vp_base; uint32_t label_base; } ssd_rom_info_t; /* debug info */ enum ssd_debug_type { SSD_DEBUG_NONE = 0, SSD_DEBUG_READ_ERR, SSD_DEBUG_WRITE_ERR, SSD_DEBUG_RW_ERR, SSD_DEBUG_READ_TO, SSD_DEBUG_WRITE_TO, SSD_DEBUG_RW_TO, SSD_DEBUG_LOG, SSD_DEBUG_OFFLINE, SSD_DEBUG_NR }; typedef struct ssd_debug_info { int type; union { struct { uint64_t off; uint32_t len; } loc; struct { int event; uint32_t extra; } log; } data; }ssd_debug_info_t; /* label */ #define SSD_LABEL_FIELD_SZ 32 #define SSD_SN_SZ 16 typedef struct ssd_label { char date[SSD_LABEL_FIELD_SZ]; char sn[SSD_LABEL_FIELD_SZ]; char part[SSD_LABEL_FIELD_SZ]; char desc[SSD_LABEL_FIELD_SZ]; char other[SSD_LABEL_FIELD_SZ]; char maf[SSD_LABEL_FIELD_SZ]; } ssd_label_t; #define SSD_LABEL_DESC_SZ 256 typedef struct ssd_labelv3 { char boardtype[SSD_LABEL_FIELD_SZ]; char barcode[SSD_LABEL_FIELD_SZ]; char item[SSD_LABEL_FIELD_SZ]; char description[SSD_LABEL_DESC_SZ]; char manufactured[SSD_LABEL_FIELD_SZ]; char vendorname[SSD_LABEL_FIELD_SZ]; char issuenumber[SSD_LABEL_FIELD_SZ]; char cleicode[SSD_LABEL_FIELD_SZ]; char bom[SSD_LABEL_FIELD_SZ]; } ssd_labelv3_t; /* battery */ typedef struct ssd_battery_info { uint32_t fw_ver; } ssd_battery_info_t; /* ssd power stat */ typedef struct ssd_power_stat { uint64_t nr_poweron; uint64_t nr_powerloss; uint64_t init_failed; } ssd_power_stat_t; /* io stat */ typedef struct ssd_io_stat { uint64_t run_time; uint64_t nr_to; uint64_t nr_ioerr; uint64_t nr_rwerr; uint64_t nr_read; uint64_t nr_write; uint64_t rsectors; uint64_t wsectors; } ssd_io_stat_t; /* ecc */ typedef struct ssd_ecc_info { uint64_t bitflip[SSD_ECC_MAX_FLIP]; } ssd_ecc_info_t; /* log */ enum ssd_log_level { SSD_LOG_LEVEL_INFO = 0, SSD_LOG_LEVEL_NOTICE, SSD_LOG_LEVEL_WARNING, SSD_LOG_LEVEL_ERR, SSD_LOG_NR_LEVEL }; typedef struct ssd_log_info { uint64_t nr_log; uint64_t stat[SSD_LOG_NR_LEVEL]; } ssd_log_info_t; /* S.M.A.R.T. */ #define SSD_SMART_MAGIC (0x5452414D53445353ull) typedef struct ssd_smart { struct ssd_power_stat pstat; struct ssd_io_stat io_stat; struct ssd_ecc_info ecc_info; struct ssd_log_info log_info; uint64_t version; uint64_t magic; } ssd_smart_t; /* internal log */ typedef struct ssd_internal_log { uint32_t nr_log; void *log; } ssd_internal_log_t; /* ssd cmd */ typedef struct ssd_cmd { struct bio *bio; struct scatterlist *sgl; struct list_head list; void *dev; int nsegs; int flag; /*pbio(1) or bio(0)*/ int tag; void *msg; dma_addr_t msg_dma; unsigned long start_time; int errors; unsigned int nr_log; struct timer_list cmd_timer; struct completion *waiting; } ssd_cmd_t; typedef void (*send_cmd_func)(struct ssd_cmd *); typedef int (*ssd_event_call)(struct gendisk *, int, int); /* gendisk, event id, event level */ /* dcmd sz */ #define SSD_DCMD_MAX_SZ 32 typedef struct ssd_dcmd { struct list_head list; void *dev; uint8_t msg[SSD_DCMD_MAX_SZ]; } ssd_dcmd_t; enum ssd_state { SSD_INIT_WORKQ, SSD_INIT_BD, SSD_ONLINE, /* full reset */ SSD_RESETING, /* hw log */ SSD_LOG_HW, /* log err */ SSD_LOG_ERR }; #define SSD_QUEUE_NAME_LEN 16 typedef struct ssd_queue { char name[SSD_QUEUE_NAME_LEN]; void *dev; int idx; uint32_t resp_idx; uint32_t resp_idx_mask; uint32_t resp_msg_sz; void *resp_msg; void *resp_ptr; struct ssd_cmd *cmd; struct ssd_io_stat io_stat; struct ssd_ecc_info ecc_info; } ssd_queue_t; typedef struct ssd_device { char name[SSD_DEV_NAME_LEN]; int idx; int major; int readonly; int int_mode; #ifdef SSD_ESCAPE_IRQ int irq_cpu; #endif int reload_fw; int ot_delay; //in ms atomic_t refcnt; atomic_t tocnt; atomic_t in_flight[2]; //r&w uint64_t uptime; struct list_head list; struct pci_dev *pdev; unsigned long mmio_base; unsigned long mmio_len; void __iomem *ctrlp; struct mutex spi_mutex; struct mutex i2c_mutex; struct ssd_protocol_info protocol_info; struct ssd_hw_info hw_info; struct ssd_rom_info rom_info; struct ssd_label label; struct ssd_smart smart; atomic_t in_sendq; spinlock_t sendq_lock; struct ssd_blist sendq; struct task_struct *send_thread; wait_queue_head_t send_waitq; atomic_t in_doneq; spinlock_t doneq_lock; struct ssd_blist doneq; struct task_struct *done_thread; wait_queue_head_t done_waitq; struct ssd_dcmd *dcmd; spinlock_t dcmd_lock; struct list_head dcmd_list; /* direct cmd list */ wait_queue_head_t dcmd_wq; unsigned long *tag_map; wait_queue_head_t tag_wq; spinlock_t cmd_lock; struct ssd_cmd *cmd; send_cmd_func scmd; ssd_event_call event_call; void *msg_base; dma_addr_t msg_base_dma; uint32_t resp_idx; void *resp_msg_base; void *resp_ptr_base; dma_addr_t resp_msg_base_dma; dma_addr_t resp_ptr_base_dma; int nr_queue; struct msix_entry entry[SSD_MSIX_VEC]; struct ssd_queue queue[SSD_MSIX_VEC]; struct request_queue *rq; /* The device request queue */ struct gendisk *gd; /* The gendisk structure */ struct mutex internal_log_mutex; struct ssd_internal_log internal_log; struct workqueue_struct *workq; struct work_struct log_work; /* get log */ void *log_buf; unsigned long state; /* device state, for example, block device inited */ struct module *owner; /* extend */ int slave; int cmajor; int save_md; int ot_protect; struct kref kref; struct mutex gd_mutex; struct ssd_log_info log_info; /* volatile */ atomic_t queue_depth; struct mutex barrier_mutex; struct mutex fw_mutex; struct ssd_hw_info_extend hw_info_ext; struct ssd_labelv3 labelv3; int wmode; int user_wmode; struct mutex bm_mutex; struct work_struct bm_work; /* check bm */ struct timer_list bm_timer; struct sfifo log_fifo; struct timer_list routine_timer; unsigned long routine_tick; unsigned long hwmon; struct work_struct hwmon_work; /* check hw */ struct work_struct capmon_work; /* check battery */ struct work_struct tempmon_work; /* check temp */ /* debug info */ struct ssd_debug_info db_info; } ssd_device_t; /* Ioctl struct */ typedef struct ssd_acc_info { uint32_t threshold_l1; uint32_t threshold_l2; uint32_t val; } ssd_acc_info_t; typedef struct ssd_reg_op_info { uint32_t offset; uint32_t value; } ssd_reg_op_info_t; typedef struct ssd_spi_op_info { void __user *buf; uint32_t off; uint32_t len; } ssd_spi_op_info_t; typedef struct ssd_i2c_op_info { uint8_t saddr; uint8_t wsize; uint8_t rsize; void __user *wbuf; void __user *rbuf; } ssd_i2c_op_info_t; typedef struct ssd_smbus_op_info { uint8_t saddr; uint8_t cmd; uint8_t size; void __user *buf; } ssd_smbus_op_info_t; typedef struct ssd_ram_op_info { uint8_t ctrl_idx; uint32_t length; uint64_t start; uint8_t __user *buf; } ssd_ram_op_info_t; typedef struct ssd_flash_op_info { uint32_t page; uint16_t flash; uint8_t chip; uint8_t ctrl_idx; uint8_t __user *buf; } ssd_flash_op_info_t; typedef struct ssd_sw_log_info { uint16_t event; uint16_t pad; uint32_t data; } ssd_sw_log_info_t; typedef struct ssd_version_info { uint32_t bridge_ver; /* bridge fw version */ uint32_t ctrl_ver; /* controller fw version */ uint32_t bm_ver; /* battery manager fw version */ uint8_t pcb_ver; /* main pcb version */ uint8_t upper_pcb_ver; uint8_t pad0; uint8_t pad1; } ssd_version_info_t; typedef struct pci_addr { uint16_t domain; uint8_t bus; uint8_t slot; uint8_t func; } pci_addr_t; typedef struct ssd_drv_param_info { int mode; int status_mask; int int_mode; int threaded_irq; int log_level; int wmode; int ot_protect; int finject; int pad[8]; } ssd_drv_param_info_t; /* form factor */ enum ssd_form_factor { SSD_FORM_FACTOR_HHHL = 0, SSD_FORM_FACTOR_FHHL }; /* ssd power loss protect */ enum ssd_plp_type { SSD_PLP_SCAP = 0, SSD_PLP_CAP, SSD_PLP_NONE }; /* ssd bm */ #define SSD_BM_SLAVE_ADDRESS 0x16 #define SSD_BM_CAP 5 /* SBS cmd */ #define SSD_BM_SAFETYSTATUS 0x51 #define SSD_BM_OPERATIONSTATUS 0x54 /* ManufacturerAccess */ #define SSD_BM_MANUFACTURERACCESS 0x00 #define SSD_BM_ENTER_CAP_LEARNING 0x0023 /* cap learning */ /* Data flash access */ #define SSD_BM_DATA_FLASH_SUBCLASS_ID 0x77 #define SSD_BM_DATA_FLASH_SUBCLASS_ID_PAGE1 0x78 #define SSD_BM_SYSTEM_DATA_SUBCLASS_ID 56 #define SSD_BM_CONFIGURATION_REGISTERS_ID 64 /* min cap voltage */ #define SSD_BM_CAP_VOLT_MIN 500 /* enum ssd_bm_cap { SSD_BM_CAP_VINA = 1, SSD_BM_CAP_JH = 3 };*/ enum ssd_bmstatus { SSD_BMSTATUS_OK = 0, SSD_BMSTATUS_CHARGING, /* not fully charged */ SSD_BMSTATUS_WARNING }; enum sbs_unit { SBS_UNIT_VALUE = 0, SBS_UNIT_TEMPERATURE, SBS_UNIT_VOLTAGE, SBS_UNIT_CURRENT, SBS_UNIT_ESR, SBS_UNIT_PERCENT, SBS_UNIT_CAPACITANCE }; enum sbs_size { SBS_SIZE_BYTE = 1, SBS_SIZE_WORD, SBS_SIZE_BLK, }; struct sbs_cmd { uint8_t cmd; uint8_t size; uint8_t unit; uint8_t off; uint16_t mask; char *desc; }; struct ssd_bm { uint16_t temp; uint16_t volt; uint16_t curr; uint16_t esr; uint16_t rsoc; uint16_t health; uint16_t cap; uint16_t chg_curr; uint16_t chg_volt; uint16_t cap_volt[SSD_BM_CAP]; uint16_t sf_alert; uint16_t sf_status; uint16_t op_status; uint16_t sys_volt; }; struct ssd_bm_manufacturer_data { uint16_t pack_lot_code; uint16_t pcb_lot_code; uint16_t firmware_ver; uint16_t hardware_ver; }; struct ssd_bm_configuration_registers { struct { uint16_t cc:3; uint16_t rsvd:5; uint16_t stack:1; uint16_t rsvd1:2; uint16_t temp:2; uint16_t rsvd2:1; uint16_t lt_en:1; uint16_t rsvd3:1; } operation_cfg; uint16_t pad; uint16_t fet_action; uint16_t pad1; uint16_t fault; }; #define SBS_VALUE_MASK 0xffff #define bm_var_offset(var) ((size_t) &((struct ssd_bm *)0)->var) #define bm_var(start, offset) ((void *) start + (offset)) static struct sbs_cmd ssd_bm_sbs[] = { {0x08, SBS_SIZE_WORD, SBS_UNIT_TEMPERATURE, bm_var_offset(temp), SBS_VALUE_MASK, "Temperature"}, {0x09, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, bm_var_offset(volt), SBS_VALUE_MASK, "Voltage"}, {0x0a, SBS_SIZE_WORD, SBS_UNIT_CURRENT, bm_var_offset(curr), SBS_VALUE_MASK, "Current"}, {0x0b, SBS_SIZE_WORD, SBS_UNIT_ESR, bm_var_offset(esr), SBS_VALUE_MASK, "ESR"}, {0x0d, SBS_SIZE_BYTE, SBS_UNIT_PERCENT, bm_var_offset(rsoc), SBS_VALUE_MASK, "RelativeStateOfCharge"}, {0x0e, SBS_SIZE_BYTE, SBS_UNIT_PERCENT, bm_var_offset(health), SBS_VALUE_MASK, "Health"}, {0x10, SBS_SIZE_WORD, SBS_UNIT_CAPACITANCE, bm_var_offset(cap), SBS_VALUE_MASK, "Capacitance"}, {0x14, SBS_SIZE_WORD, SBS_UNIT_CURRENT, bm_var_offset(chg_curr), SBS_VALUE_MASK, "ChargingCurrent"}, {0x15, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, bm_var_offset(chg_volt), SBS_VALUE_MASK, "ChargingVoltage"}, {0x3b, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[4]), SBS_VALUE_MASK, "CapacitorVoltage5"}, {0x3c, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[3]), SBS_VALUE_MASK, "CapacitorVoltage4"}, {0x3d, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[2]), SBS_VALUE_MASK, "CapacitorVoltage3"}, {0x3e, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[1]), SBS_VALUE_MASK, "CapacitorVoltage2"}, {0x3f, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, (uint8_t)bm_var_offset(cap_volt[0]), SBS_VALUE_MASK, "CapacitorVoltage1"}, {0x50, SBS_SIZE_WORD, SBS_UNIT_VALUE, bm_var_offset(sf_alert), 0x870F, "SafetyAlert"}, {0x51, SBS_SIZE_WORD, SBS_UNIT_VALUE, bm_var_offset(sf_status), 0xE7BF, "SafetyStatus"}, {0x54, SBS_SIZE_WORD, SBS_UNIT_VALUE, bm_var_offset(op_status), 0x79F4, "OperationStatus"}, {0x5a, SBS_SIZE_WORD, SBS_UNIT_VOLTAGE, bm_var_offset(sys_volt), SBS_VALUE_MASK, "SystemVoltage"}, {0, 0, 0, 0, 0, NULL}, }; /* ssd ioctl */ #define SSD_CMD_GET_PROTOCOL_INFO _IOR('H', 100, struct ssd_protocol_info) #define SSD_CMD_GET_HW_INFO _IOR('H', 101, struct ssd_hw_info) #define SSD_CMD_GET_ROM_INFO _IOR('H', 102, struct ssd_rom_info) #define SSD_CMD_GET_SMART _IOR('H', 103, struct ssd_smart) #define SSD_CMD_GET_IDX _IOR('H', 105, int) #define SSD_CMD_GET_AMOUNT _IOR('H', 106, int) #define SSD_CMD_GET_TO_INFO _IOR('H', 107, int) #define SSD_CMD_GET_DRV_VER _IOR('H', 108, char[DRIVER_VERSION_LEN]) #define SSD_CMD_GET_BBACC_INFO _IOR('H', 109, struct ssd_acc_info) #define SSD_CMD_GET_ECACC_INFO _IOR('H', 110, struct ssd_acc_info) #define SSD_CMD_GET_HW_INFO_EXT _IOR('H', 111, struct ssd_hw_info_extend) #define SSD_CMD_REG_READ _IOWR('H', 120, struct ssd_reg_op_info) #define SSD_CMD_REG_WRITE _IOWR('H', 121, struct ssd_reg_op_info) #define SSD_CMD_SPI_READ _IOWR('H', 125, struct ssd_spi_op_info) #define SSD_CMD_SPI_WRITE _IOWR('H', 126, struct ssd_spi_op_info) #define SSD_CMD_SPI_ERASE _IOWR('H', 127, struct ssd_spi_op_info) #define SSD_CMD_I2C_READ _IOWR('H', 128, struct ssd_i2c_op_info) #define SSD_CMD_I2C_WRITE _IOWR('H', 129, struct ssd_i2c_op_info) #define SSD_CMD_I2C_WRITE_READ _IOWR('H', 130, struct ssd_i2c_op_info) #define SSD_CMD_SMBUS_SEND_BYTE _IOWR('H', 131, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_RECEIVE_BYTE _IOWR('H', 132, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_WRITE_BYTE _IOWR('H', 133, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_READ_BYTE _IOWR('H', 135, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_WRITE_WORD _IOWR('H', 136, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_READ_WORD _IOWR('H', 137, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_WRITE_BLOCK _IOWR('H', 138, struct ssd_smbus_op_info) #define SSD_CMD_SMBUS_READ_BLOCK _IOWR('H', 139, struct ssd_smbus_op_info) #define SSD_CMD_BM_GET_VER _IOR('H', 140, uint16_t) #define SSD_CMD_BM_GET_NR_CAP _IOR('H', 141, int) #define SSD_CMD_BM_CAP_LEARNING _IOW('H', 142, int) #define SSD_CMD_CAP_LEARN _IOR('H', 143, uint32_t) #define SSD_CMD_GET_CAP_STATUS _IOR('H', 144, int) #define SSD_CMD_RAM_READ _IOWR('H', 150, struct ssd_ram_op_info) #define SSD_CMD_RAM_WRITE _IOWR('H', 151, struct ssd_ram_op_info) #define SSD_CMD_NAND_READ_ID _IOR('H', 160, struct ssd_flash_op_info) #define SSD_CMD_NAND_READ _IOWR('H', 161, struct ssd_flash_op_info) //with oob #define SSD_CMD_NAND_WRITE _IOWR('H', 162, struct ssd_flash_op_info) #define SSD_CMD_NAND_ERASE _IOWR('H', 163, struct ssd_flash_op_info) #define SSD_CMD_NAND_READ_EXT _IOWR('H', 164, struct ssd_flash_op_info) //ingore EIO #define SSD_CMD_UPDATE_BBT _IOW('H', 180, struct ssd_flash_op_info) #define SSD_CMD_CLEAR_ALARM _IOW('H', 190, int) #define SSD_CMD_SET_ALARM _IOW('H', 191, int) #define SSD_CMD_RESET _IOW('H', 200, int) #define SSD_CMD_RELOAD_FW _IOW('H', 201, int) #define SSD_CMD_UNLOAD_DEV _IOW('H', 202, int) #define SSD_CMD_LOAD_DEV _IOW('H', 203, int) #define SSD_CMD_UPDATE_VP _IOWR('H', 205, uint32_t) #define SSD_CMD_FULL_RESET _IOW('H', 206, int) #define SSD_CMD_GET_NR_LOG _IOR('H', 220, uint32_t) #define SSD_CMD_GET_LOG _IOR('H', 221, void *) #define SSD_CMD_LOG_LEVEL _IOW('H', 222, int) #define SSD_CMD_OT_PROTECT _IOW('H', 223, int) #define SSD_CMD_GET_OT_STATUS _IOR('H', 224, int) #define SSD_CMD_CLEAR_LOG _IOW('H', 230, int) #define SSD_CMD_CLEAR_SMART _IOW('H', 231, int) #define SSD_CMD_SW_LOG _IOW('H', 232, struct ssd_sw_log_info) #define SSD_CMD_GET_LABEL _IOR('H', 235, struct ssd_label) #define SSD_CMD_GET_VERSION _IOR('H', 236, struct ssd_version_info) #define SSD_CMD_GET_TEMPERATURE _IOR('H', 237, int) #define SSD_CMD_GET_BMSTATUS _IOR('H', 238, int) #define SSD_CMD_GET_LABEL2 _IOR('H', 239, void *) #define SSD_CMD_FLUSH _IOW('H', 240, int) #define SSD_CMD_SAVE_MD _IOW('H', 241, int) #define SSD_CMD_SET_WMODE _IOW('H', 242, int) #define SSD_CMD_GET_WMODE _IOR('H', 243, int) #define SSD_CMD_GET_USER_WMODE _IOR('H', 244, int) #define SSD_CMD_DEBUG _IOW('H', 250, struct ssd_debug_info) #define SSD_CMD_DRV_PARAM_INFO _IOR('H', 251, struct ssd_drv_param_info) /* log */ #define SSD_LOG_MAX_SZ 4096 #define SSD_LOG_LEVEL SSD_LOG_LEVEL_NOTICE enum ssd_log_data { SSD_LOG_DATA_NONE = 0, SSD_LOG_DATA_LOC, SSD_LOG_DATA_HEX }; typedef struct ssd_log_entry { union { struct { uint32_t page:10; uint32_t block:14; uint32_t flash:8; } loc; struct { uint32_t page:12; uint32_t block:12; uint32_t flash:8; } loc1; uint32_t val; } data; uint16_t event:10; uint16_t mod:6; uint16_t idx; }__attribute__((packed))ssd_log_entry_t; typedef struct ssd_log { uint64_t time:56; uint64_t ctrl_idx:8; ssd_log_entry_t le; } __attribute__((packed)) ssd_log_t; typedef struct ssd_log_desc { uint16_t event; uint8_t level; uint8_t data; uint8_t sblock; uint8_t spage; char *desc; } __attribute__((packed)) ssd_log_desc_t; #define SSD_LOG_SW_IDX 0xF #define SSD_UNKNOWN_EVENT ((uint16_t)-1) static struct ssd_log_desc ssd_log_desc[] = { /* event, level, show flash, show block, show page, desc */ {0x0, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC, 0, 0, "Create BBT failure"}, //g3 {0x1, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC, 0, 0, "Read BBT failure"}, //g3 {0x2, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Mark bad block"}, {0x3, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Flush BBT failure"}, {0x4, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0x7, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "No available blocks"}, {0x8, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Bad EC header"}, {0x9, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC, 1, 0, "Bad VID header"}, //g3 {0xa, SSD_LOG_LEVEL_INFO, SSD_LOG_DATA_LOC, 1, 0, "Wear leveling"}, {0xb, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "WL read back failure"}, {0x11, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Data recovery failure"}, // err {0x20, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Init: scan mapping table failure"}, // err g3 {0x21, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0x22, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0x23, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0x24, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Merge: read mapping page failure"}, {0x25, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Merge: read back failure"}, {0x26, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0x27, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC, 1, 1, "Data corrupted for abnormal power down"}, //g3 {0x28, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Merge: mapping page corrupted"}, {0x29, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Init: no mapping page"}, {0x2a, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: mapping pages incomplete"}, {0x2b, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Read back failure after programming failure"}, // err {0xf1, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Read failure without recovery"}, // err {0xf2, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 0, 0, "No available blocks"}, // maybe err g3 {0xf3, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 0, "Init: RAID incomplete"}, // err g3 {0xf4, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0xf5, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read failure in moving data"}, {0xf6, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Program failure"}, {0xf7, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_LOC, 1, 1, "Init: RAID not complete"}, {0xf8, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Init: data moving interrupted"}, {0xfe, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Data inspection failure"}, {0xff, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "IO: ECC failed"}, /* new */ {0x2e, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 0, 0, "No available reserved blocks" }, // err {0x30, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Init: PMT membership not found"}, {0x31, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Init: PMT corrupted"}, {0x32, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Init: PBT membership not found"}, {0x33, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Init: PBT not found"}, {0x34, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Init: PBT corrupted"}, {0x35, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PMT page read failure"}, {0x36, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PBT page read failure"}, {0x37, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PBT backup page read failure"}, {0x38, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PBMT read failure"}, {0x39, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Init: PBMT scan failure"}, // err {0x3a, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: first page read failure"}, {0x3b, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Init: first page scan failure"}, // err {0x3c, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Init: scan unclosed block failure"}, // err {0x3d, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: write pointer mismatch"}, {0x3e, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PMT recovery: PBMT read failure"}, {0x3f, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Init: PMT recovery: PBMT scan failure"}, {0x40, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "Init: PMT recovery: data page read failure"}, //err {0x41, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PBT write pointer mismatch"}, {0x42, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: PBT latest version corrupted"}, {0x43, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 0, "Init: too many unclosed blocks"}, {0x44, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Init: PDW block found"}, {0x45, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_HEX, 0, 0, "Init: more than one PDW block found"}, //err {0x46, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Init: first page is blank or read failure"}, {0x47, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Init: PDW block not found"}, {0x50, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 0, "Cache: hit error data"}, // err {0x51, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 0, "Cache: read back failure"}, // err {0x52, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Cache: unknown command"}, //? {0x53, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_LOC, 1, 1, "GC/WL read back failure"}, // err {0x60, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "Erase failure"}, {0x70, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "LPA not matched"}, {0x71, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "PBN not matched"}, {0x72, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read retry failure"}, {0x73, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Need raid recovery"}, {0x74, SSD_LOG_LEVEL_INFO, SSD_LOG_DATA_LOC, 1, 1, "Need read retry"}, {0x75, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read invalid data page"}, {0x76, SSD_LOG_LEVEL_INFO, SSD_LOG_DATA_LOC, 1, 1, "ECC error, data in cache, PBN matched"}, {0x77, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "ECC error, data in cache, PBN not matched"}, {0x78, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "ECC error, data in flash, PBN not matched"}, {0x79, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "ECC ok, data in cache, LPA not matched"}, {0x7a, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "ECC ok, data in flash, LPA not matched"}, {0x7b, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID data in cache, LPA not matched"}, {0x7c, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID data in flash, LPA not matched"}, {0x7d, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read data page status error"}, {0x7e, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read blank page"}, {0x7f, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Access flash timeout"}, {0x80, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "EC overflow"}, {0x81, SSD_LOG_LEVEL_INFO, SSD_LOG_DATA_NONE, 0, 0, "Scrubbing completed"}, {0x82, SSD_LOG_LEVEL_INFO, SSD_LOG_DATA_LOC, 1, 0, "Unstable block(too much bit flip)"}, {0x83, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "GC: ram error"}, //? {0x84, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "GC: one PBMT read failure"}, {0x88, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "GC: mark bad block"}, {0x89, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 0, "GC: invalid page count error"}, // maybe err {0x8a, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "Warning: Bad Block close to limit"}, {0x8b, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_NONE, 0, 0, "Error: Bad Block over limit"}, {0x8c, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "Warning: P/E cycles close to limit"}, {0x8d, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_NONE, 0, 0, "Error: P/E cycles over limit"}, {0x90, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Over temperature"}, //xx {0x91, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Temperature is OK"}, //xx {0x92, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "Battery fault"}, {0x93, SSD_LOG_LEVEL_WARNING, SSD_LOG_DATA_NONE, 0, 0, "SEU fault"}, //err {0x94, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_NONE, 0, 0, "DDR error"}, //err {0x95, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_NONE, 0, 0, "Controller serdes error"}, //err {0x96, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_NONE, 0, 0, "Bridge serdes 1 error"}, //err {0x97, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_NONE, 0, 0, "Bridge serdes 2 error"}, //err {0x98, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "SEU fault (corrected)"}, //err {0x99, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Battery is OK"}, {0x9a, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Temperature close to limit"}, //xx {0x9b, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "SEU fault address (low)"}, {0x9c, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "SEU fault address (high)"}, {0x9d, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "I2C fault" }, {0x9e, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "DDR single bit error" }, {0x9f, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Board voltage fault" }, {0xa0, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "LPA not matched"}, {0xa1, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Re-read data in cache"}, {0xa2, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read blank page"}, {0xa3, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID recovery: Read blank page"}, {0xa4, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID recovery: new data in cache"}, {0xa5, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID recovery: PBN not matched"}, {0xa6, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Read data with error flag"}, {0xa7, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID recovery: recoverd data with error flag"}, {0xa8, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Blank page in cache, PBN matched"}, {0xa9, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID recovery: Blank page in cache, PBN matched"}, {0xaa, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 0, 0, "Flash init failure"}, {0xab, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "Mapping table recovery failure"}, {0xac, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_LOC, 1, 1, "RAID recovery: ECC failed"}, {0xb0, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Temperature is up to degree 95"}, {0xb1, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Temperature is up to degree 100"}, {0x300, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_HEX, 0, 0, "CMD timeout"}, {0x301, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Power on"}, {0x302, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Power off"}, {0x303, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Clear log"}, {0x304, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Set capacity"}, {0x305, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Clear data"}, {0x306, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "BM safety status"}, {0x307, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_HEX, 0, 0, "I/O error"}, {0x308, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "CMD error"}, {0x309, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Set wmode"}, {0x30a, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_HEX, 0, 0, "DDR init failed" }, {0x30b, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "PCIe link status" }, {0x30c, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_HEX, 0, 0, "Controller reset sync error" }, {0x30d, SSD_LOG_LEVEL_ERR, SSD_LOG_DATA_HEX, 0, 0, "Clock fault" }, {0x30e, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "FPGA voltage fault status" }, {0x30f, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Set capacity finished"}, {0x310, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Clear data finished"}, {0x311, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Reset"}, {0x312, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_HEX, 0, 0, "CAP: voltage fault"}, {0x313, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_NONE, 0, 0, "CAP: learn fault"}, {0x314, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "CAP status"}, {0x315, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "Board voltage fault status"}, {0x316, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Inlet over temperature"}, {0x317, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Inlet temperature is OK"}, {0x318, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Flash over temperature"}, {0x319, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Flash temperature is OK"}, {0x31a, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_NONE, 0, 0, "CAP: short circuit"}, {0x31b, SSD_LOG_LEVEL_WARNING,SSD_LOG_DATA_HEX, 0, 0, "Sensor fault"}, {0x31c, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Erase all data"}, {0x31d, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_NONE, 0, 0, "Erase all data finished"}, {SSD_UNKNOWN_EVENT, SSD_LOG_LEVEL_NOTICE, SSD_LOG_DATA_HEX, 0, 0, "unknown event"}, }; /* */ #define SSD_LOG_OVER_TEMP 0x90 #define SSD_LOG_NORMAL_TEMP 0x91 #define SSD_LOG_WARN_TEMP 0x9a #define SSD_LOG_SEU_FAULT 0x93 #define SSD_LOG_SEU_FAULT1 0x98 #define SSD_LOG_BATTERY_FAULT 0x92 #define SSD_LOG_BATTERY_OK 0x99 #define SSD_LOG_BOARD_VOLT_FAULT 0x9f /* software log */ #define SSD_LOG_TIMEOUT 0x300 #define SSD_LOG_POWER_ON 0x301 #define SSD_LOG_POWER_OFF 0x302 #define SSD_LOG_CLEAR_LOG 0x303 #define SSD_LOG_SET_CAPACITY 0x304 #define SSD_LOG_CLEAR_DATA 0x305 #define SSD_LOG_BM_SFSTATUS 0x306 #define SSD_LOG_EIO 0x307 #define SSD_LOG_ECMD 0x308 #define SSD_LOG_SET_WMODE 0x309 #define SSD_LOG_DDR_INIT_ERR 0x30a #define SSD_LOG_PCIE_LINK_STATUS 0x30b #define SSD_LOG_CTRL_RST_SYNC 0x30c #define SSD_LOG_CLK_FAULT 0x30d #define SSD_LOG_VOLT_FAULT 0x30e #define SSD_LOG_SET_CAPACITY_END 0x30F #define SSD_LOG_CLEAR_DATA_END 0x310 #define SSD_LOG_RESET 0x311 #define SSD_LOG_CAP_VOLT_FAULT 0x312 #define SSD_LOG_CAP_LEARN_FAULT 0x313 #define SSD_LOG_CAP_STATUS 0x314 #define SSD_LOG_VOLT_STATUS 0x315 #define SSD_LOG_INLET_OVER_TEMP 0x316 #define SSD_LOG_INLET_NORMAL_TEMP 0x317 #define SSD_LOG_FLASH_OVER_TEMP 0x318 #define SSD_LOG_FLASH_NORMAL_TEMP 0x319 #define SSD_LOG_CAP_SHORT_CIRCUIT 0x31a #define SSD_LOG_SENSOR_FAULT 0x31b #define SSD_LOG_ERASE_ALL 0x31c #define SSD_LOG_ERASE_ALL_END 0x31d /* sw log fifo depth */ #define SSD_LOG_FIFO_SZ 1024 /* done queue */ static DEFINE_PER_CPU(struct list_head, ssd_doneq); static DEFINE_PER_CPU(struct tasklet_struct, ssd_tasklet); /* unloading driver */ static volatile int ssd_exiting = 0; #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)) static struct class_simple *ssd_class; #else static struct class *ssd_class; #endif static int ssd_cmajor = SSD_CMAJOR; /* ssd block device major, minors */ static int ssd_major = SSD_MAJOR; static int ssd_major_sl = SSD_MAJOR_SL; static int ssd_minors = SSD_MINORS; /* ssd device list */ static struct list_head ssd_list; static unsigned long ssd_index_bits[SSD_MAX_DEV / BITS_PER_LONG + 1]; static unsigned long ssd_index_bits_sl[SSD_MAX_DEV / BITS_PER_LONG + 1]; static atomic_t ssd_nr; /* module param */ enum ssd_drv_mode { SSD_DRV_MODE_STANDARD = 0, /* full */ SSD_DRV_MODE_DEBUG = 2, /* debug */ SSD_DRV_MODE_BASE /* base only */ }; enum ssd_int_mode { SSD_INT_LEGACY = 0, SSD_INT_MSI, SSD_INT_MSIX }; #if (defined SSD_MSIX) #define SSD_INT_MODE_DEFAULT SSD_INT_MSIX #elif (defined SSD_MSI) #define SSD_INT_MODE_DEFAULT SSD_INT_MSI #else /* auto select the defaut int mode according to the kernel version*/ /* suse 11 sp1 irqbalance bug: use msi instead*/ #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR >= 6) || (defined RHEL_MAJOR && RHEL_MAJOR == 5 && RHEL_MINOR >= 5)) #define SSD_INT_MODE_DEFAULT SSD_INT_MSIX #else #define SSD_INT_MODE_DEFAULT SSD_INT_MSI #endif #endif static int mode = SSD_DRV_MODE_STANDARD; static int status_mask = 0xFF; static int int_mode = SSD_INT_MODE_DEFAULT; static int threaded_irq = 0; static int log_level = SSD_LOG_LEVEL_WARNING; static int ot_protect = 1; static int wmode = SSD_WMODE_DEFAULT; static int finject = 0; module_param(mode, int, 0); module_param(status_mask, int, 0); module_param(int_mode, int, 0); module_param(threaded_irq, int, 0); module_param(log_level, int, 0); module_param(ot_protect, int, 0); module_param(wmode, int, 0); module_param(finject, int, 0); MODULE_PARM_DESC(mode, "driver mode, 0 - standard, 1 - debug, 2 - debug without IO, 3 - basic debug mode"); MODULE_PARM_DESC(status_mask, "command status mask, 0 - without command error, 0xff - with command error"); MODULE_PARM_DESC(int_mode, "preferred interrupt mode, 0 - legacy, 1 - msi, 2 - msix"); MODULE_PARM_DESC(threaded_irq, "threaded irq, 0 - normal irq, 1 - threaded irq"); MODULE_PARM_DESC(log_level, "log level to display, 0 - info and above, 1 - notice and above, 2 - warning and above, 3 - error only"); MODULE_PARM_DESC(ot_protect, "over temperature protect, 0 - disable, 1 - enable"); MODULE_PARM_DESC(wmode, "write mode, 0 - write buffer (with risk for the 6xx firmware), 1 - write buffer ex, 2 - write through, 3 - auto, 4 - default"); MODULE_PARM_DESC(finject, "enable fault simulation, 0 - off, 1 - on, for debug purpose only"); #ifndef MODULE static int __init ssd_drv_mode(char *str) { mode = (int)simple_strtoul(str, NULL, 0); return 1; } static int __init ssd_status_mask(char *str) { status_mask = (int)simple_strtoul(str, NULL, 16); return 1; } static int __init ssd_int_mode(char *str) { int_mode = (int)simple_strtoul(str, NULL, 0); return 1; } static int __init ssd_threaded_irq(char *str) { threaded_irq = (int)simple_strtoul(str, NULL, 0); return 1; } static int __init ssd_log_level(char *str) { log_level = (int)simple_strtoul(str, NULL, 0); return 1; } static int __init ssd_ot_protect(char *str) { ot_protect = (int)simple_strtoul(str, NULL, 0); return 1; } static int __init ssd_wmode(char *str) { wmode = (int)simple_strtoul(str, NULL, 0); return 1; } static int __init ssd_finject(char *str) { finject = (int)simple_strtoul(str, NULL, 0); return 1; } __setup(MODULE_NAME"_mode=", ssd_drv_mode); __setup(MODULE_NAME"_status_mask=", ssd_status_mask); __setup(MODULE_NAME"_int_mode=", ssd_int_mode); __setup(MODULE_NAME"_threaded_irq=", ssd_threaded_irq); __setup(MODULE_NAME"_log_level=", ssd_log_level); __setup(MODULE_NAME"_ot_protect=", ssd_ot_protect); __setup(MODULE_NAME"_wmode=", ssd_wmode); __setup(MODULE_NAME"_finject=", ssd_finject); #endif #ifdef CONFIG_PROC_FS #include #include #define SSD_PROC_DIR MODULE_NAME #define SSD_PROC_INFO "info" static struct proc_dir_entry *ssd_proc_dir = NULL; static struct proc_dir_entry *ssd_proc_info = NULL; #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)) static int ssd_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data) { struct ssd_device *dev = NULL; struct ssd_device *n = NULL; uint64_t size; int idx; int len = 0; //char type; //xx if (ssd_exiting) { return 0; } len += snprintf((page + len), (count - len), "Driver Version:\t%s\n", DRIVER_VERSION); list_for_each_entry_safe(dev, n, &ssd_list, list) { idx = dev->idx + 1; size = dev->hw_info.size ; do_div(size, 1000000000); len += snprintf((page + len), (count - len), "\n"); len += snprintf((page + len), (count - len), "HIO %d Size:\t%uGB\n", idx, (uint32_t)size); len += snprintf((page + len), (count - len), "HIO %d Bridge FW VER:\t%03X\n", idx, dev->hw_info.bridge_ver); if (dev->hw_info.ctrl_ver != 0) { len += snprintf((page + len), (count - len), "HIO %d Controller FW VER:\t%03X\n", idx, dev->hw_info.ctrl_ver); } len += snprintf((page + len), (count - len), "HIO %d PCB VER:\t.%c\n", idx, dev->hw_info.pcb_ver); if (dev->hw_info.upper_pcb_ver >= 'A') { len += snprintf((page + len), (count - len), "HIO %d Upper PCB VER:\t.%c\n", idx, dev->hw_info.upper_pcb_ver); } len += snprintf((page + len), (count - len), "HIO %d Device:\t%s\n", idx, dev->name); } return len; } #else static int ssd_proc_show(struct seq_file *m, void *v) { struct ssd_device *dev = NULL; struct ssd_device *n = NULL; uint64_t size; int idx; if (ssd_exiting) { return 0; } seq_printf(m, "Driver Version:\t%s\n", DRIVER_VERSION); list_for_each_entry_safe(dev, n, &ssd_list, list) { idx = dev->idx + 1; size = dev->hw_info.size ; do_div(size, 1000000000); seq_printf(m, "\n"); seq_printf(m, "HIO %d Size:\t%uGB\n", idx, (uint32_t)size); seq_printf(m, "HIO %d Bridge FW VER:\t%03X\n", idx, dev->hw_info.bridge_ver); if (dev->hw_info.ctrl_ver != 0) { seq_printf(m, "HIO %d Controller FW VER:\t%03X\n", idx, dev->hw_info.ctrl_ver); } seq_printf(m, "HIO %d PCB VER:\t.%c\n", idx, dev->hw_info.pcb_ver); if (dev->hw_info.upper_pcb_ver >= 'A') { seq_printf(m, "HIO %d Upper PCB VER:\t.%c\n", idx, dev->hw_info.upper_pcb_ver); } seq_printf(m, "HIO %d Device:\t%s\n", idx, dev->name); } return 0; } static int ssd_proc_open(struct inode *inode, struct file *file) { #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,9,0)) return single_open(file, ssd_proc_show, PDE(inode)->data); #else return single_open(file, ssd_proc_show, PDE_DATA(inode)); #endif } static const struct file_operations ssd_proc_fops = { .open = ssd_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif static void ssd_cleanup_proc(void) { if (ssd_proc_info) { remove_proc_entry(SSD_PROC_INFO, ssd_proc_dir); ssd_proc_info = NULL; } if (ssd_proc_dir) { remove_proc_entry(SSD_PROC_DIR, NULL); ssd_proc_dir = NULL; } } static int ssd_init_proc(void) { ssd_proc_dir = proc_mkdir(SSD_PROC_DIR, NULL); if (!ssd_proc_dir) goto out_proc_mkdir; #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)) ssd_proc_info = create_proc_entry(SSD_PROC_INFO, S_IFREG | S_IRUGO | S_IWUSR, ssd_proc_dir); if (!ssd_proc_info) goto out_create_proc_entry; ssd_proc_info->read_proc = ssd_proc_read; /* kernel bug */ #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) ssd_proc_info->owner = THIS_MODULE; #endif #else ssd_proc_info = proc_create(SSD_PROC_INFO, 0600, ssd_proc_dir, &ssd_proc_fops); if (!ssd_proc_info) goto out_create_proc_entry; #endif return 0; out_create_proc_entry: remove_proc_entry(SSD_PROC_DIR, NULL); out_proc_mkdir: return -ENOMEM; } #else static void ssd_cleanup_proc(void) { return; } static int ssd_init_proc(void) { return 0; } #endif /* CONFIG_PROC_FS */ /* sysfs */ static void ssd_unregister_sysfs(struct ssd_device *dev) { return; } static int ssd_register_sysfs(struct ssd_device *dev) { return 0; } static void ssd_cleanup_sysfs(void) { return; } static int ssd_init_sysfs(void) { return 0; } static inline void ssd_put_index(int slave, int index) { unsigned long *index_bits = ssd_index_bits; if (slave) { index_bits = ssd_index_bits_sl; } if (test_and_clear_bit(index, index_bits)) { atomic_dec(&ssd_nr); } } static inline int ssd_get_index(int slave) { unsigned long *index_bits = ssd_index_bits; int index; if (slave) { index_bits = ssd_index_bits_sl; } find_index: if ((index = find_first_zero_bit(index_bits, SSD_MAX_DEV)) >= SSD_MAX_DEV) { return -1; } if (test_and_set_bit(index, index_bits)) { goto find_index; } atomic_inc(&ssd_nr); return index; } static void ssd_cleanup_index(void) { return; } static int ssd_init_index(void) { INIT_LIST_HEAD(&ssd_list); atomic_set(&ssd_nr, 0); memset(ssd_index_bits, 0, (SSD_MAX_DEV / BITS_PER_LONG + 1)); memset(ssd_index_bits_sl, 0, (SSD_MAX_DEV / BITS_PER_LONG + 1)); return 0; } static void ssd_set_dev_name(char *name, size_t size, int idx) { if(idx < SSD_ALPHABET_NUM) { snprintf(name, size, "%c", 'a'+idx); } else { idx -= SSD_ALPHABET_NUM; snprintf(name, size, "%c%c", 'a'+(idx/SSD_ALPHABET_NUM), 'a'+(idx%SSD_ALPHABET_NUM)); } } /* pci register r&w */ static inline void ssd_reg_write(void *addr, uint64_t val) { iowrite32((uint32_t)val, addr); iowrite32((uint32_t)(val >> 32), addr + 4); wmb(); } static inline uint64_t ssd_reg_read(void *addr) { uint64_t val; uint32_t val_lo, val_hi; val_lo = ioread32(addr); val_hi = ioread32(addr + 4); rmb(); val = val_lo | ((uint64_t)val_hi << 32); return val; } #define ssd_reg32_write(addr, val) writel(val, addr) #define ssd_reg32_read(addr) readl(addr) /* alarm led */ static void ssd_clear_alarm(struct ssd_device *dev) { uint32_t val; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return; } val = ssd_reg32_read(dev->ctrlp + SSD_LED_REG); /* firmware control */ val &= ~0x2; ssd_reg32_write(dev->ctrlp + SSD_LED_REG, val); } static void ssd_set_alarm(struct ssd_device *dev) { uint32_t val; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return; } val = ssd_reg32_read(dev->ctrlp + SSD_LED_REG); /* light up */ val &= ~0x1; /* software control */ val |= 0x2; ssd_reg32_write(dev->ctrlp + SSD_LED_REG, val); } #define u32_swap(x) \ ((uint32_t)( \ (((uint32_t)(x) & (uint32_t)0x000000ffUL) << 24) | \ (((uint32_t)(x) & (uint32_t)0x0000ff00UL) << 8) | \ (((uint32_t)(x) & (uint32_t)0x00ff0000UL) >> 8) | \ (((uint32_t)(x) & (uint32_t)0xff000000UL) >> 24))) #define u16_swap(x) \ ((uint16_t)( \ (((uint16_t)(x) & (uint16_t)0x00ff) << 8) | \ (((uint16_t)(x) & (uint16_t)0xff00) >> 8) )) #if 0 /* No lock, for init only*/ static int ssd_spi_read_id(struct ssd_device *dev, uint32_t *id) { uint32_t val; unsigned long st; int ret = 0; if (!dev || !id) { return -EINVAL; } ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_ID); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); st = jiffies; for (;;) { val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); if (val == 0x1000000) { break; } if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_ID); *id = val; out: return ret; } #endif /* spi access */ static int ssd_init_spi(struct ssd_device *dev) { uint32_t val; unsigned long st; int ret = 0; mutex_lock(&dev->spi_mutex); st = jiffies; for(;;) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_STATUS); do { val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } while (val != 0x1000000); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_STATUS); if (!(val & 0x1)) { break; } if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } out: if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if (val & 0x1) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_CLSR); } } ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_DISABLE); mutex_unlock(&dev->spi_mutex); ret = 0; return ret; } static int ssd_spi_page_read(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size) { uint32_t val; uint32_t rlen = 0; unsigned long st; int ret = 0; if (!dev || !buf) { return -EINVAL; } if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || ((uint64_t)off + (uint64_t)size) > dev->rom_info.size || size > dev->rom_info.page_size) { return -EINVAL; } mutex_lock(&dev->spi_mutex); while (rlen < size) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD_HI, ((off + rlen) >> 24)); wmb(); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, (((off + rlen) << 8) | SSD_SPI_CMD_READ)); (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); st = jiffies; for (;;) { val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); if (val == 0x1000000) { break; } if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_RDATA); *(uint32_t *)(buf + rlen)= u32_swap(val); rlen += sizeof(uint32_t); } out: mutex_unlock(&dev->spi_mutex); return ret; } static int ssd_spi_page_write(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size) { uint32_t val; uint32_t wlen; unsigned long st; int i; int ret = 0; if (!dev || !buf) { return -EINVAL; } if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || ((uint64_t)off + (uint64_t)size) > dev->rom_info.size || size > dev->rom_info.page_size || (off / dev->rom_info.page_size) != ((off + size - 1) / dev->rom_info.page_size)) { return -EINVAL; } mutex_lock(&dev->spi_mutex); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_ENABLE); wlen = size / sizeof(uint32_t); for (i=0; i<(int)wlen; i++) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_WDATA, u32_swap(*((uint32_t *)buf + i))); } wmb(); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD_HI, (off >> 24)); wmb(); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, ((off << 8) | SSD_SPI_CMD_PROGRAM)); udelay(1); st = jiffies; for (;;) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_STATUS); do { val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } while (val != 0x1000000); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_STATUS); if (!(val & 0x1)) { break; } if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if ((val >> 6) & 0x1) { ret = -EIO; goto out; } } out: if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if (val & 0x1) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_CLSR); } } ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_DISABLE); mutex_unlock(&dev->spi_mutex); return ret; } static int ssd_spi_block_erase(struct ssd_device *dev, uint32_t off) { uint32_t val; unsigned long st; int ret = 0; if (!dev) { return -EINVAL; } if ((off % dev->rom_info.block_size) != 0 || off >= dev->rom_info.size) { return -EINVAL; } mutex_lock(&dev->spi_mutex); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_ENABLE); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_ENABLE); wmb(); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD_HI, (off >> 24)); wmb(); ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, ((off << 8) | SSD_SPI_CMD_ERASE)); st = jiffies; for (;;) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_READ_STATUS); do { val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_READY); if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } while (val != 0x1000000); val = ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_STATUS); if (!(val & 0x1)) { break; } if (time_after(jiffies, (st + SSD_SPI_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if ((val >> 5) & 0x1) { ret = -EIO; goto out; } } out: if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if (val & 0x1) { ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_CLSR); } } ssd_reg32_write(dev->ctrlp + SSD_SPI_REG_CMD, SSD_SPI_CMD_W_DISABLE); mutex_unlock(&dev->spi_mutex); return ret; } static int ssd_spi_read(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size) { uint32_t len = 0; uint32_t roff; uint32_t rsize; int ret = 0; if (!dev || !buf) { return -EINVAL; } if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || ((uint64_t)off + (uint64_t)size) > dev->rom_info.size) { return -EINVAL; } while (len < size) { roff = (off + len) % dev->rom_info.page_size; rsize = dev->rom_info.page_size - roff; if ((size - len) < rsize) { rsize = (size - len); } roff = off + len; ret = ssd_spi_page_read(dev, (buf + len), roff, rsize); if (ret) { goto out; } len += rsize; cond_resched(); } out: return ret; } static int ssd_spi_write(struct ssd_device *dev, void *buf, uint32_t off, uint32_t size) { uint32_t len = 0; uint32_t woff; uint32_t wsize; int ret = 0; if (!dev || !buf) { return -EINVAL; } if ((off % sizeof(uint32_t)) != 0 || (size % sizeof(uint32_t)) != 0 || size == 0 || ((uint64_t)off + (uint64_t)size) > dev->rom_info.size) { return -EINVAL; } while (len < size) { woff = (off + len) % dev->rom_info.page_size; wsize = dev->rom_info.page_size - woff; if ((size - len) < wsize) { wsize = (size - len); } woff = off + len; ret = ssd_spi_page_write(dev, (buf + len), woff, wsize); if (ret) { goto out; } len += wsize; cond_resched(); } out: return ret; } static int ssd_spi_erase(struct ssd_device *dev, uint32_t off, uint32_t size) { uint32_t len = 0; uint32_t eoff; int ret = 0; if (!dev) { return -EINVAL; } if (size == 0 || ((uint64_t)off + (uint64_t)size) > dev->rom_info.size || (off % dev->rom_info.block_size) != 0 || (size % dev->rom_info.block_size) != 0) { return -EINVAL; } while (len < size) { eoff = (off + len); ret = ssd_spi_block_erase(dev, eoff); if (ret) { goto out; } len += dev->rom_info.block_size; cond_resched(); } out: return ret; } /* i2c access */ static uint32_t __ssd_i2c_reg32_read(void *addr) { return ssd_reg32_read(addr); } static void __ssd_i2c_reg32_write(void *addr, uint32_t val) { ssd_reg32_write(addr, val); ssd_reg32_read(addr); } static int __ssd_i2c_clear(struct ssd_device *dev, uint8_t saddr) { ssd_i2c_ctrl_t ctrl; ssd_i2c_data_t data; uint8_t status = 0; int nr_data = 0; unsigned long st; int ret = 0; check_status: ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_STATUS_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); st = jiffies; for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } status = data.bits.rdata; if (!(status & 0x4)) { /* clear read fifo data */ ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_DATA_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); st = jiffies; for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out; } cond_resched(); } nr_data++; if (nr_data <= SSD_I2C_MAX_DATA) { goto check_status; } else { goto out_reset; } } if (status & 0x3) { /* clear int */ ctrl.bits.wdata = 0x04; ctrl.bits.addr = SSD_I2C_CMD_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); } if (!(status & 0x8)) { out_reset: /* reset i2c controller */ ctrl.bits.wdata = 0x0; ctrl.bits.addr = SSD_I2C_RESET_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); } out: return ret; } static int ssd_i2c_write(struct ssd_device *dev, uint8_t saddr, uint8_t size, uint8_t *buf) { ssd_i2c_ctrl_t ctrl; ssd_i2c_data_t data; uint8_t off = 0; uint8_t status = 0; unsigned long st; int ret = 0; mutex_lock(&dev->i2c_mutex); ctrl.val = 0; /* slave addr */ ctrl.bits.wdata = saddr; ctrl.bits.addr = SSD_I2C_SADDR_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* data */ while (off < size) { ctrl.bits.wdata = buf[off]; ctrl.bits.addr = SSD_I2C_DATA_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); off++; } /* write */ ctrl.bits.wdata = 0x01; ctrl.bits.addr = SSD_I2C_CMD_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* wait */ st = jiffies; for (;;) { ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_STATUS_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } status = data.bits.rdata; if (status & 0x1) { break; } if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } if (!(status & 0x1)) { ret = -1; goto out_clear; } /* busy ? */ if (status & 0x20) { ret = -2; goto out_clear; } /* ack ? */ if (status & 0x10) { ret = -3; goto out_clear; } /* clear */ out_clear: if (__ssd_i2c_clear(dev, saddr)) { if (!ret) ret = -4; } mutex_unlock(&dev->i2c_mutex); return ret; } static int ssd_i2c_read(struct ssd_device *dev, uint8_t saddr, uint8_t size, uint8_t *buf) { ssd_i2c_ctrl_t ctrl; ssd_i2c_data_t data; uint8_t off = 0; uint8_t status = 0; unsigned long st; int ret = 0; mutex_lock(&dev->i2c_mutex); ctrl.val = 0; /* slave addr */ ctrl.bits.wdata = saddr; ctrl.bits.addr = SSD_I2C_SADDR_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* read len */ ctrl.bits.wdata = size; ctrl.bits.addr = SSD_I2C_LEN_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* read */ ctrl.bits.wdata = 0x02; ctrl.bits.addr = SSD_I2C_CMD_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* wait */ st = jiffies; for (;;) { ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_STATUS_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } status = data.bits.rdata; if (status & 0x2) { break; } if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } if (!(status & 0x2)) { ret = -1; goto out_clear; } /* busy ? */ if (status & 0x20) { ret = -2; goto out_clear; } /* ack ? */ if (status & 0x10) { ret = -3; goto out_clear; } /* data */ while (off < size) { ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_DATA_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); st = jiffies; for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } buf[off] = data.bits.rdata; off++; } /* clear */ out_clear: if (__ssd_i2c_clear(dev, saddr)) { if (!ret) ret = -4; } mutex_unlock(&dev->i2c_mutex); return ret; } static int ssd_i2c_write_read(struct ssd_device *dev, uint8_t saddr, uint8_t wsize, uint8_t *wbuf, uint8_t rsize, uint8_t *rbuf) { ssd_i2c_ctrl_t ctrl; ssd_i2c_data_t data; uint8_t off = 0; uint8_t status = 0; unsigned long st; int ret = 0; mutex_lock(&dev->i2c_mutex); ctrl.val = 0; /* slave addr */ ctrl.bits.wdata = saddr; ctrl.bits.addr = SSD_I2C_SADDR_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* data */ off = 0; while (off < wsize) { ctrl.bits.wdata = wbuf[off]; ctrl.bits.addr = SSD_I2C_DATA_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); off++; } /* read len */ ctrl.bits.wdata = rsize; ctrl.bits.addr = SSD_I2C_LEN_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* write -> read */ ctrl.bits.wdata = 0x03; ctrl.bits.addr = SSD_I2C_CMD_REG; ctrl.bits.rw = SSD_I2C_CTRL_WRITE; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); /* wait */ st = jiffies; for (;;) { ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_STATUS_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } status = data.bits.rdata; if (status & 0x2) { break; } if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } if (!(status & 0x2)) { ret = -1; goto out_clear; } /* busy ? */ if (status & 0x20) { ret = -2; goto out_clear; } /* ack ? */ if (status & 0x10) { ret = -3; goto out_clear; } /* data */ off = 0; while (off < rsize) { ctrl.bits.wdata = 0; ctrl.bits.addr = SSD_I2C_DATA_REG; ctrl.bits.rw = SSD_I2C_CTRL_READ; __ssd_i2c_reg32_write(dev->ctrlp + SSD_I2C_CTRL_REG, ctrl.val); st = jiffies; for (;;) { data.val = __ssd_i2c_reg32_read(dev->ctrlp + SSD_I2C_RDATA_REG); if (data.bits.valid == 0) { break; } /* retry */ if (time_after(jiffies, (st + SSD_I2C_TIMEOUT))) { ret = -ETIMEDOUT; goto out_clear; } cond_resched(); } rbuf[off] = data.bits.rdata; off++; } /* clear */ out_clear: if (__ssd_i2c_clear(dev, saddr)) { if (!ret) ret = -4; } mutex_unlock(&dev->i2c_mutex); return ret; } static int ssd_smbus_send_byte(struct ssd_device *dev, uint8_t saddr, uint8_t *buf) { int i = 0; int ret = 0; for (;;) { ret = ssd_i2c_write(dev, saddr, 1, buf); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_receive_byte(struct ssd_device *dev, uint8_t saddr, uint8_t *buf) { int i = 0; int ret = 0; for (;;) { ret = ssd_i2c_read(dev, saddr, 1, buf); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_write_byte(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf) { uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0}; int i = 0; int ret = 0; smb_data[0] = cmd; memcpy((smb_data + 1), buf, 1); for (;;) { ret = ssd_i2c_write(dev, saddr, 2, smb_data); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_read_byte(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf) { uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0}; int i = 0; int ret = 0; smb_data[0] = cmd; for (;;) { ret = ssd_i2c_write_read(dev, saddr, 1, smb_data, 1, buf); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_write_word(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf) { uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0}; int i = 0; int ret = 0; smb_data[0] = cmd; memcpy((smb_data + 1), buf, 2); for (;;) { ret = ssd_i2c_write(dev, saddr, 3, smb_data); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_read_word(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t *buf) { uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0}; int i = 0; int ret = 0; smb_data[0] = cmd; for (;;) { ret = ssd_i2c_write_read(dev, saddr, 1, smb_data, 2, buf); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_write_block(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t size, uint8_t *buf) { uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0}; int i = 0; int ret = 0; smb_data[0] = cmd; smb_data[1] = size; memcpy((smb_data + 2), buf, size); for (;;) { ret = ssd_i2c_write(dev, saddr, (2 + size), smb_data); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } return ret; } static int ssd_smbus_read_block(struct ssd_device *dev, uint8_t saddr, uint8_t cmd, uint8_t size, uint8_t *buf) { uint8_t smb_data[SSD_SMBUS_DATA_MAX] = {0}; uint8_t rsize; int i = 0; int ret = 0; smb_data[0] = cmd; for (;;) { ret = ssd_i2c_write_read(dev, saddr, 1, smb_data, (SSD_SMBUS_BLOCK_MAX + 1), (smb_data + 1)); if (!ret || -ETIMEDOUT == ret) { break; } i++; if (i >= SSD_SMBUS_RETRY_MAX) { break; } msleep(SSD_SMBUS_RETRY_INTERVAL); } if (ret) { return ret; } rsize = smb_data[1]; if (rsize > size ) { rsize = size; } memcpy(buf, (smb_data + 2), rsize); return 0; } static int ssd_gen_swlog(struct ssd_device *dev, uint16_t event, uint32_t data); /* sensor */ static int ssd_init_lm75(struct ssd_device *dev, uint8_t saddr) { uint8_t conf = 0; int ret = 0; ret = ssd_smbus_read_byte(dev, saddr, SSD_LM75_REG_CONF, &conf); if (ret) { goto out; } conf &= (uint8_t)(~1u); ret = ssd_smbus_write_byte(dev, saddr, SSD_LM75_REG_CONF, &conf); if (ret) { goto out; } out: return ret; } static int ssd_lm75_read(struct ssd_device *dev, uint8_t saddr, uint16_t *data) { uint16_t val = 0; int ret; ret = ssd_smbus_read_word(dev, saddr, SSD_LM75_REG_TEMP, (uint8_t *)&val); if (ret) { return ret; } *data = u16_swap(val); return 0; } static int ssd_init_lm80(struct ssd_device *dev, uint8_t saddr) { uint8_t val; uint8_t low, high; int i; int ret = 0; /* init */ val = 0x80; ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_CONFIG, &val); if (ret) { goto out; } /* 11-bit temp */ val = 0x08; ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_RES, &val); if (ret) { goto out; } /* set volt limit */ for (i=0; ihw_info.nr_ctrl <= 1 && SSD_LM80_IN_1V2 == i) { high = 0xFF; low = 0; } /* high limit */ ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_IN_MAX(i), &high); if (ret) { goto out; } /* low limit*/ ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_IN_MIN(i), &low); if (ret) { goto out; } } /* set interrupt mask: allow volt in interrupt except cap in*/ val = 0x81; ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK1, &val); if (ret) { goto out; } /* set interrupt mask: disable others */ val = 0xFF; ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK2, &val); if (ret) { goto out; } /* start */ val = 0x03; ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_CONFIG, &val); if (ret) { goto out; } out: return ret; } static int ssd_lm80_enable_in(struct ssd_device *dev, uint8_t saddr, int idx) { uint8_t val = 0; int ret = 0; if (idx >= SSD_LM80_IN_NR || idx < 0) { return -EINVAL; } ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_MASK1, &val); if (ret) { goto out; } val &= ~(1UL << (uint32_t)idx); ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK1, &val); if (ret) { goto out; } out: return ret; } static int ssd_lm80_disable_in(struct ssd_device *dev, uint8_t saddr, int idx) { uint8_t val = 0; int ret = 0; if (idx >= SSD_LM80_IN_NR || idx < 0) { return -EINVAL; } ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_MASK1, &val); if (ret) { goto out; } val |= (1UL << (uint32_t)idx); ret = ssd_smbus_write_byte(dev, saddr, SSD_LM80_REG_MASK1, &val); if (ret) { goto out; } out: return ret; } static int ssd_lm80_read_temp(struct ssd_device *dev, uint8_t saddr, uint16_t *data) { uint16_t val = 0; int ret; ret = ssd_smbus_read_word(dev, saddr, SSD_LM80_REG_TEMP, (uint8_t *)&val); if (ret) { return ret; } *data = u16_swap(val); return 0; } static int ssd_lm80_check_event(struct ssd_device *dev, uint8_t saddr) { uint32_t volt; uint16_t val = 0, status; uint8_t alarm1 = 0, alarm2 = 0; int i; int ret = 0; /* read interrupt status to clear interrupt */ ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_ALARM1, &alarm1); if (ret) { goto out; } ret = ssd_smbus_read_byte(dev, saddr, SSD_LM80_REG_ALARM2, &alarm2); if (ret) { goto out; } status = (uint16_t)alarm1 | ((uint16_t)alarm2 << 8); /* parse inetrrupt status */ for (i=0; i> (uint32_t)i) & 0x1)) { if (test_and_clear_bit(SSD_HWMON_LM80(i), &dev->hwmon)) { /* enable INx irq */ ret = ssd_lm80_enable_in(dev, saddr, i); if (ret) { goto out; } } continue; } /* disable INx irq */ ret = ssd_lm80_disable_in(dev, saddr, i); if (ret) { goto out; } if (test_and_set_bit(SSD_HWMON_LM80(i), &dev->hwmon)) { continue; } ret = ssd_smbus_read_word(dev, saddr, SSD_LM80_REG_IN(i), (uint8_t *)&val); if (ret) { goto out; } volt = SSD_LM80_CONVERT_VOLT(u16_swap(val)); switch (i) { case SSD_LM80_IN_CAP: { if (0 == volt) { ssd_gen_swlog(dev, SSD_LOG_CAP_SHORT_CIRCUIT, 0); } else { ssd_gen_swlog(dev, SSD_LOG_CAP_VOLT_FAULT, SSD_PL_CAP_VOLT(volt)); } break; } case SSD_LM80_IN_1V2: case SSD_LM80_IN_1V2a: case SSD_LM80_IN_1V5: case SSD_LM80_IN_1V8: { ssd_gen_swlog(dev, SSD_LOG_VOLT_STATUS, SSD_VOLT_LOG_DATA(i, 0, volt)); break; } case SSD_LM80_IN_FPGA_3V3: case SSD_LM80_IN_3V3: { ssd_gen_swlog(dev, SSD_LOG_VOLT_STATUS, SSD_VOLT_LOG_DATA(i, 0, SSD_LM80_3V3_VOLT(volt))); break; } default: break; } } out: if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, (uint32_t)saddr); } } else { test_and_clear_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon); } return ret; } static int ssd_init_sensor(struct ssd_device *dev) { int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { goto out; } ret = ssd_init_lm75(dev, SSD_SENSOR_LM75_SADDRESS); if (ret) { hio_warn("%s: init lm75 failed\n", dev->name); if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM75), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM75_SADDRESS); } goto out; } if (dev->hw_info.pcb_ver >= 'B' || dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_HHHL) { ret = ssd_init_lm80(dev, SSD_SENSOR_LM80_SADDRESS); if (ret) { hio_warn("%s: init lm80 failed\n", dev->name); if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS); } goto out; } } out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } /* board volt */ static int ssd_mon_boardvolt(struct ssd_device *dev) { if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return 0; } if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') { return 0; } return ssd_lm80_check_event(dev, SSD_SENSOR_LM80_SADDRESS); } /* temperature */ static int ssd_mon_temp(struct ssd_device *dev) { int cur; uint16_t val = 0; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return 0; } if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') { return 0; } /* inlet */ ret = ssd_lm80_read_temp(dev, SSD_SENSOR_LM80_SADDRESS, &val); if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS); } goto out; } test_and_clear_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon); cur = SSD_SENSOR_CONVERT_TEMP(val); if (cur >= SSD_INLET_OT_TEMP) { if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_INLET), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_INLET_OVER_TEMP, (uint32_t)cur); } } else if(cur < SSD_INLET_OT_HYST) { if (test_and_clear_bit(SSD_HWMON_TEMP(SSD_TEMP_INLET), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_INLET_NORMAL_TEMP, (uint32_t)cur); } } /* flash */ ret = ssd_lm75_read(dev, SSD_SENSOR_LM75_SADDRESS, &val); if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM75), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM75_SADDRESS); } goto out; } test_and_clear_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM75), &dev->hwmon); cur = SSD_SENSOR_CONVERT_TEMP(val); if (cur >= SSD_FLASH_OT_TEMP) { if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_FLASH), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_FLASH_OVER_TEMP, (uint32_t)cur); } } else if(cur < SSD_FLASH_OT_HYST) { if (test_and_clear_bit(SSD_HWMON_TEMP(SSD_TEMP_FLASH), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_FLASH_NORMAL_TEMP, (uint32_t)cur); } } out: return ret; } /* cmd tag */ static inline void ssd_put_tag(struct ssd_device *dev, int tag) { test_and_clear_bit(tag, dev->tag_map); wake_up(&dev->tag_wq); } static inline int ssd_get_tag(struct ssd_device *dev, int wait) { int tag; find_tag: while ((tag = find_first_zero_bit(dev->tag_map, dev->hw_info.cmd_fifo_sz)) >= atomic_read(&dev->queue_depth)) { DEFINE_WAIT(__wait); if (!wait) { return -1; } prepare_to_wait_exclusive(&dev->tag_wq, &__wait, TASK_UNINTERRUPTIBLE); schedule(); finish_wait(&dev->tag_wq, &__wait); } if (test_and_set_bit(tag, dev->tag_map)) { goto find_tag; } return tag; } static void ssd_barrier_put_tag(struct ssd_device *dev, int tag) { test_and_clear_bit(tag, dev->tag_map); } static int ssd_barrier_get_tag(struct ssd_device *dev) { int tag = 0; if (test_and_set_bit(tag, dev->tag_map)) { return -1; } return tag; } static void ssd_barrier_end(struct ssd_device *dev) { atomic_set(&dev->queue_depth, dev->hw_info.cmd_fifo_sz); wake_up_all(&dev->tag_wq); mutex_unlock(&dev->barrier_mutex); } static int ssd_barrier_start(struct ssd_device *dev) { int i; mutex_lock(&dev->barrier_mutex); atomic_set(&dev->queue_depth, 0); for (i=0; itag_map, dev->hw_info.cmd_fifo_sz) >= dev->hw_info.cmd_fifo_sz) { return 0; } __set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(1); } atomic_set(&dev->queue_depth, dev->hw_info.cmd_fifo_sz); wake_up_all(&dev->tag_wq); mutex_unlock(&dev->barrier_mutex); return -EBUSY; } static int ssd_busy(struct ssd_device *dev) { if (find_first_bit(dev->tag_map, dev->hw_info.cmd_fifo_sz) >= dev->hw_info.cmd_fifo_sz) { return 0; } return 1; } static int ssd_wait_io(struct ssd_device *dev) { int i; for (i=0; itag_map, dev->hw_info.cmd_fifo_sz) >= dev->hw_info.cmd_fifo_sz) { return 0; } __set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(1); } return -EBUSY; } #if 0 static int ssd_in_barrier(struct ssd_device *dev) { return (0 == atomic_read(&dev->queue_depth)); } #endif static void ssd_cleanup_tag(struct ssd_device *dev) { kfree(dev->tag_map); } static int ssd_init_tag(struct ssd_device *dev) { int nr_ulongs = ALIGN(dev->hw_info.cmd_fifo_sz, BITS_PER_LONG) / BITS_PER_LONG; mutex_init(&dev->barrier_mutex); atomic_set(&dev->queue_depth, dev->hw_info.cmd_fifo_sz); dev->tag_map = kmalloc(nr_ulongs * sizeof(unsigned long), GFP_ATOMIC); if (!dev->tag_map) { return -ENOMEM; } memset(dev->tag_map, 0, nr_ulongs * sizeof(unsigned long)); init_waitqueue_head(&dev->tag_wq); return 0; } /* io stat */ static void ssd_end_io_acct(struct ssd_cmd *cmd) { struct ssd_device *dev = cmd->dev; struct bio *bio = cmd->bio; unsigned long dur = jiffies - cmd->start_time; int rw = bio_data_dir(bio); #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6 && RHEL_MINOR >= 7)) int cpu = part_stat_lock(); struct hd_struct *part = disk_map_sector_rcu(dev->gd, bio_start(bio)); part_round_stats(cpu, part); part_stat_add(cpu, part, ticks[rw], dur); part_dec_in_flight(part, rw); part_stat_unlock(); #elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27)) int cpu = part_stat_lock(); struct hd_struct *part = &dev->gd->part0; part_round_stats(cpu, part); part_stat_add(cpu, part, ticks[rw], dur); part_stat_unlock(); part->in_flight[rw] = atomic_dec_return(&dev->in_flight[rw]); #elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14)) preempt_disable(); disk_round_stats(dev->gd); preempt_enable(); disk_stat_add(dev->gd, ticks[rw], dur); dev->gd->in_flight = atomic_dec_return(&dev->in_flight[0]); #else preempt_disable(); disk_round_stats(dev->gd); preempt_enable(); if (rw == WRITE) { disk_stat_add(dev->gd, write_ticks, dur); } else { disk_stat_add(dev->gd, read_ticks, dur); } dev->gd->in_flight = atomic_dec_return(&dev->in_flight[0]); #endif } static void ssd_start_io_acct(struct ssd_cmd *cmd) { struct ssd_device *dev = cmd->dev; struct bio *bio = cmd->bio; int rw = bio_data_dir(bio); #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6 && RHEL_MINOR >= 7)) int cpu = part_stat_lock(); struct hd_struct *part = disk_map_sector_rcu(dev->gd, bio_start(bio)); part_round_stats(cpu, part); part_stat_inc(cpu, part, ios[rw]); part_stat_add(cpu, part, sectors[rw], bio_sectors(bio)); part_inc_in_flight(part, rw); part_stat_unlock(); #elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27)) int cpu = part_stat_lock(); struct hd_struct *part = &dev->gd->part0; part_round_stats(cpu, part); part_stat_inc(cpu, part, ios[rw]); part_stat_add(cpu, part, sectors[rw], bio_sectors(bio)); part_stat_unlock(); part->in_flight[rw] = atomic_inc_return(&dev->in_flight[rw]); #elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14)) preempt_disable(); disk_round_stats(dev->gd); preempt_enable(); disk_stat_inc(dev->gd, ios[rw]); disk_stat_add(dev->gd, sectors[rw], bio_sectors(bio)); dev->gd->in_flight = atomic_inc_return(&dev->in_flight[0]); #else preempt_disable(); disk_round_stats(dev->gd); preempt_enable(); if (rw == WRITE) { disk_stat_inc(dev->gd, writes); disk_stat_add(dev->gd, write_sectors, bio_sectors(bio)); } else { disk_stat_inc(dev->gd, reads); disk_stat_add(dev->gd, read_sectors, bio_sectors(bio)); } dev->gd->in_flight = atomic_inc_return(&dev->in_flight[0]); #endif cmd->start_time = jiffies; } /* io */ static void ssd_queue_bio(struct ssd_device *dev, struct bio *bio) { spin_lock(&dev->sendq_lock); ssd_blist_add(&dev->sendq, bio); spin_unlock(&dev->sendq_lock); atomic_inc(&dev->in_sendq); wake_up(&dev->send_waitq); } static inline void ssd_end_request(struct ssd_cmd *cmd) { struct ssd_device *dev = cmd->dev; struct bio *bio = cmd->bio; int errors = cmd->errors; int tag = cmd->tag; if (bio) { #if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36))) if (!(bio->bi_rw & REQ_DISCARD)) { ssd_end_io_acct(cmd); if (!cmd->flag) { pci_unmap_sg(dev->pdev, cmd->sgl, cmd->nsegs, bio_data_dir(bio) == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); } } #elif (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))) if (!bio_rw_flagged(bio, BIO_RW_DISCARD)) { ssd_end_io_acct(cmd); if (!cmd->flag) { pci_unmap_sg(dev->pdev, cmd->sgl, cmd->nsegs, bio_data_dir(bio) == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); } } #else ssd_end_io_acct(cmd); if (!cmd->flag) { pci_unmap_sg(dev->pdev, cmd->sgl, cmd->nsegs, bio_data_dir(bio) == READ ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); } #endif cmd->bio = NULL; ssd_put_tag(dev, tag); if (SSD_INT_MSIX == dev->int_mode || tag < 16 || errors) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, errors); #else bio_endio(bio, bio->bi_size, errors); #endif } else /* if (bio->bi_idx >= bio->bi_vcnt)*/ { spin_lock(&dev->doneq_lock); ssd_blist_add(&dev->doneq, bio); spin_unlock(&dev->doneq_lock); atomic_inc(&dev->in_doneq); wake_up(&dev->done_waitq); } } else { if (cmd->waiting) { complete(cmd->waiting); } } } static void ssd_end_timeout_request(struct ssd_cmd *cmd) { struct ssd_device *dev = cmd->dev; struct ssd_rw_msg *msg = (struct ssd_rw_msg *)cmd->msg; int i; for (i=0; inr_queue; i++) { disable_irq(dev->entry[i].vector); } atomic_inc(&dev->tocnt); //if (cmd->bio) { hio_err("%s: cmd timeout: tag %d fun %#x\n", dev->name, msg->tag, msg->fun); cmd->errors = -ETIMEDOUT; ssd_end_request(cmd); //} for (i=0; inr_queue; i++) { enable_irq(dev->entry[i].vector); } /* alarm led */ ssd_set_alarm(dev); } /* cmd timer */ static void ssd_cmd_add_timer(struct ssd_cmd *cmd, int timeout, void (*complt)(struct ssd_cmd *)) { init_timer(&cmd->cmd_timer); cmd->cmd_timer.data = (unsigned long)cmd; cmd->cmd_timer.expires = jiffies + timeout; cmd->cmd_timer.function = (void (*)(unsigned long)) complt; add_timer(&cmd->cmd_timer); } static int ssd_cmd_del_timer(struct ssd_cmd *cmd) { return del_timer(&cmd->cmd_timer); } static void ssd_add_timer(struct timer_list *timer, int timeout, void (*complt)(void *), void *data) { init_timer(timer); timer->data = (unsigned long)data; timer->expires = jiffies + timeout; timer->function = (void (*)(unsigned long)) complt; add_timer(timer); } static int ssd_del_timer(struct timer_list *timer) { return del_timer(timer); } static void ssd_cmd_timeout(struct ssd_cmd *cmd) { struct ssd_device *dev = cmd->dev; uint32_t msg = *(uint32_t *)cmd->msg; ssd_end_timeout_request(cmd); ssd_gen_swlog(dev, SSD_LOG_TIMEOUT, msg); } static void __ssd_done(unsigned long data) { struct ssd_cmd *cmd; LIST_HEAD(localq); local_irq_disable(); #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)) list_splice_init(&__get_cpu_var(ssd_doneq), &localq); #else list_splice_init(this_cpu_ptr(&ssd_doneq), &localq); #endif local_irq_enable(); while (!list_empty(&localq)) { cmd = list_entry(localq.next, struct ssd_cmd, list); list_del_init(&cmd->list); ssd_end_request(cmd); } } static void __ssd_done_db(unsigned long data) { struct ssd_cmd *cmd; struct ssd_device *dev; struct bio *bio; LIST_HEAD(localq); local_irq_disable(); #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)) list_splice_init(&__get_cpu_var(ssd_doneq), &localq); #else list_splice_init(this_cpu_ptr(&ssd_doneq), &localq); #endif local_irq_enable(); while (!list_empty(&localq)) { cmd = list_entry(localq.next, struct ssd_cmd, list); list_del_init(&cmd->list); dev = (struct ssd_device *)cmd->dev; bio = cmd->bio; if (bio) { sector_t off = dev->db_info.data.loc.off; uint32_t len = dev->db_info.data.loc.len; switch (dev->db_info.type) { case SSD_DEBUG_READ_ERR: if (bio_data_dir(bio) == READ && !((off + len) <= bio_start(bio) || off >= (bio_start(bio) + bio_sectors(bio)))) { cmd->errors = -EIO; } break; case SSD_DEBUG_WRITE_ERR: if (bio_data_dir(bio) == WRITE && !((off + len) <= bio_start(bio) || off >= (bio_start(bio) + bio_sectors(bio)))) { cmd->errors = -EROFS; } break; case SSD_DEBUG_RW_ERR: if (!((off + len) <= bio_start(bio) || off >= (bio_start(bio) + bio_sectors(bio)))) { if (bio_data_dir(bio) == READ) { cmd->errors = -EIO; } else { cmd->errors = -EROFS; } } break; default: break; } } ssd_end_request(cmd); } } static inline void ssd_done_bh(struct ssd_cmd *cmd) { unsigned long flags = 0; if (unlikely(!ssd_cmd_del_timer(cmd))) { struct ssd_device *dev = cmd->dev; struct ssd_rw_msg *msg = (struct ssd_rw_msg *)cmd->msg; hio_err("%s: unknown cmd: tag %d fun %#x\n", dev->name, msg->tag, msg->fun); /* alarm led */ ssd_set_alarm(dev); return; } local_irq_save(flags); #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)) list_add_tail(&cmd->list, &__get_cpu_var(ssd_doneq)); tasklet_hi_schedule(&__get_cpu_var(ssd_tasklet)); #else list_add_tail(&cmd->list, this_cpu_ptr(&ssd_doneq)); tasklet_hi_schedule(this_cpu_ptr(&ssd_tasklet)); #endif local_irq_restore(flags); return; } static inline void ssd_done(struct ssd_cmd *cmd) { if (unlikely(!ssd_cmd_del_timer(cmd))) { struct ssd_device *dev = cmd->dev; struct ssd_rw_msg *msg = (struct ssd_rw_msg *)cmd->msg; hio_err("%s: unknown cmd: tag %d fun %#x\n", dev->name, msg->tag, msg->fun); /* alarm led */ ssd_set_alarm(dev); return; } ssd_end_request(cmd); return; } static inline void ssd_dispatch_cmd(struct ssd_cmd *cmd) { struct ssd_device *dev = (struct ssd_device *)cmd->dev; ssd_cmd_add_timer(cmd, SSD_CMD_TIMEOUT, ssd_cmd_timeout); spin_lock(&dev->cmd_lock); ssd_reg_write(dev->ctrlp + SSD_REQ_FIFO_REG, cmd->msg_dma); spin_unlock(&dev->cmd_lock); } static inline void ssd_send_cmd(struct ssd_cmd *cmd) { struct ssd_device *dev = (struct ssd_device *)cmd->dev; ssd_cmd_add_timer(cmd, SSD_CMD_TIMEOUT, ssd_cmd_timeout); ssd_reg32_write(dev->ctrlp + SSD_REQ_FIFO_REG, ((uint32_t)cmd->tag | ((uint32_t)cmd->nsegs << 16))); } static inline void ssd_send_cmd_db(struct ssd_cmd *cmd) { struct ssd_device *dev = (struct ssd_device *)cmd->dev; struct bio *bio = cmd->bio; ssd_cmd_add_timer(cmd, SSD_CMD_TIMEOUT, ssd_cmd_timeout); if (bio) { switch (dev->db_info.type) { case SSD_DEBUG_READ_TO: if (bio_data_dir(bio) == READ) { return; } break; case SSD_DEBUG_WRITE_TO: if (bio_data_dir(bio) == WRITE) { return; } break; case SSD_DEBUG_RW_TO: return; break; default: break; } } ssd_reg32_write(dev->ctrlp + SSD_REQ_FIFO_REG, ((uint32_t)cmd->tag | ((uint32_t)cmd->nsegs << 16))); } /* fixed for BIOVEC_PHYS_MERGEABLE */ #ifdef SSD_BIOVEC_PHYS_MERGEABLE_FIXED #include #include #include static bool xen_biovec_phys_mergeable_fixed(const struct bio_vec *vec1, const struct bio_vec *vec2) { unsigned long mfn1 = pfn_to_mfn(page_to_pfn(vec1->bv_page)); unsigned long mfn2 = pfn_to_mfn(page_to_pfn(vec2->bv_page)); return __BIOVEC_PHYS_MERGEABLE(vec1, vec2) && ((mfn1 == mfn2) || ((mfn1+1) == mfn2)); } #ifdef BIOVEC_PHYS_MERGEABLE #undef BIOVEC_PHYS_MERGEABLE #endif #define BIOVEC_PHYS_MERGEABLE(vec1, vec2) \ (__BIOVEC_PHYS_MERGEABLE(vec1, vec2) && \ (!xen_domain() || xen_biovec_phys_mergeable_fixed(vec1, vec2))) #endif static inline int ssd_bio_map_sg(struct ssd_device *dev, struct bio *bio, struct scatterlist *sgl) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)) struct bio_vec *bvec, *bvprv = NULL; struct scatterlist *sg = NULL; int i = 0, nsegs = 0; #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)) sg_init_table(sgl, dev->hw_info.cmd_max_sg); #endif /* * for each segment in bio */ bio_for_each_segment(bvec, bio, i) { if (bvprv && BIOVEC_PHYS_MERGEABLE(bvprv, bvec)) { sg->length += bvec->bv_len; } else { if (unlikely(nsegs >= (int)dev->hw_info.cmd_max_sg)) { break; } sg = sg ? (sg + 1) : sgl; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) sg_set_page(sg, bvec->bv_page, bvec->bv_len, bvec->bv_offset); #else sg->page = bvec->bv_page; sg->length = bvec->bv_len; sg->offset = bvec->bv_offset; #endif nsegs++; } bvprv = bvec; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) if (sg) { sg_mark_end(sg); } #endif bio->bi_idx = i; return nsegs; #else struct bio_vec bvec, bvprv; struct bvec_iter iter; struct scatterlist *sg = NULL; int nsegs = 0; int first = 1; sg_init_table(sgl, dev->hw_info.cmd_max_sg); /* * for each segment in bio */ bio_for_each_segment(bvec, bio, iter) { if (!first && BIOVEC_PHYS_MERGEABLE(&bvprv, &bvec)) { sg->length += bvec.bv_len; } else { if (unlikely(nsegs >= (int)dev->hw_info.cmd_max_sg)) { break; } sg = sg ? (sg + 1) : sgl; sg_set_page(sg, bvec.bv_page, bvec.bv_len, bvec.bv_offset); nsegs++; first = 0; } bvprv = bvec; } if (sg) { sg_mark_end(sg); } return nsegs; #endif } static int __ssd_submit_pbio(struct ssd_device *dev, struct bio *bio, int wait) { struct ssd_cmd *cmd; struct ssd_rw_msg *msg; struct ssd_sg_entry *sge; sector_t block = bio_start(bio); int tag; int i; tag = ssd_get_tag(dev, wait); if (tag < 0) { return -EBUSY; } cmd = &dev->cmd[tag]; cmd->bio = bio; cmd->flag = 1; msg = (struct ssd_rw_msg *)cmd->msg; #if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36))) if (bio->bi_rw & REQ_DISCARD) { unsigned int length = bio_sectors(bio); //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block); msg->tag = tag; msg->fun = SSD_FUNC_TRIM; sge = msg->sge; for (i=0; i<(dev->hw_info.cmd_max_sg); i++) { sge->block = block; sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length; sge->buf = 0; block += sge->length; length -= sge->length; sge++; if (length <= 0) { break; } } msg->nsegs = cmd->nsegs = (i + 1); dev->scmd(cmd); return 0; } #elif (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))) if (bio_rw_flagged(bio, BIO_RW_DISCARD)) { unsigned int length = bio_sectors(bio); //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block); msg->tag = tag; msg->fun = SSD_FUNC_TRIM; sge = msg->sge; for (i=0; i<(dev->hw_info.cmd_max_sg); i++) { sge->block = block; sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length; sge->buf = 0; block += sge->length; length -= sge->length; sge++; if (length <= 0) { break; } } msg->nsegs = cmd->nsegs = (i + 1); dev->scmd(cmd); return 0; } #endif //msg->nsegs = cmd->nsegs = ssd_bio_map_sg(dev, bio, sgl); msg->nsegs = cmd->nsegs = bio->bi_vcnt; //xx if (bio_data_dir(bio) == READ) { msg->fun = SSD_FUNC_READ; msg->flag = 0; } else { msg->fun = SSD_FUNC_WRITE; msg->flag = dev->wmode; } sge = msg->sge; for (i=0; ibi_vcnt; i++) { sge->block = block; sge->length = bio->bi_io_vec[i].bv_len >> 9; sge->buf = (uint64_t)((void *)bio->bi_io_vec[i].bv_page + bio->bi_io_vec[i].bv_offset); block += sge->length; sge++; } msg->tag = tag; #ifdef SSD_OT_PROTECT if (unlikely(dev->ot_delay > 0 && dev->ot_protect != 0)) { msleep_interruptible(dev->ot_delay); } #endif ssd_start_io_acct(cmd); dev->scmd(cmd); return 0; } static inline int ssd_submit_bio(struct ssd_device *dev, struct bio *bio, int wait) { struct ssd_cmd *cmd; struct ssd_rw_msg *msg; struct ssd_sg_entry *sge; struct scatterlist *sgl; sector_t block = bio_start(bio); int tag; int i; tag = ssd_get_tag(dev, wait); if (tag < 0) { return -EBUSY; } cmd = &dev->cmd[tag]; cmd->bio = bio; cmd->flag = 0; msg = (struct ssd_rw_msg *)cmd->msg; sgl = cmd->sgl; #if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36))) if (bio->bi_rw & REQ_DISCARD) { unsigned int length = bio_sectors(bio); //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block); msg->tag = tag; msg->fun = SSD_FUNC_TRIM; sge = msg->sge; for (i=0; i<(dev->hw_info.cmd_max_sg); i++) { sge->block = block; sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length; sge->buf = 0; block += sge->length; length -= sge->length; sge++; if (length <= 0) { break; } } msg->nsegs = cmd->nsegs = (i + 1); dev->scmd(cmd); return 0; } #elif (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))) if (bio_rw_flagged(bio, BIO_RW_DISCARD)) { unsigned int length = bio_sectors(bio); //printk(KERN_WARNING "%s: discard len %u, block %llu\n", dev->name, bio_sectors(bio), block); msg->tag = tag; msg->fun = SSD_FUNC_TRIM; sge = msg->sge; for (i=0; i<(dev->hw_info.cmd_max_sg); i++) { sge->block = block; sge->length = (length >= dev->hw_info.sg_max_sec) ? dev->hw_info.sg_max_sec : length; sge->buf = 0; block += sge->length; length -= sge->length; sge++; if (length <= 0) { break; } } msg->nsegs = cmd->nsegs = (i + 1); dev->scmd(cmd); return 0; } #endif msg->nsegs = cmd->nsegs = ssd_bio_map_sg(dev, bio, sgl); //xx if (bio_data_dir(bio) == READ) { msg->fun = SSD_FUNC_READ; msg->flag = 0; pci_map_sg(dev->pdev, sgl, cmd->nsegs, PCI_DMA_FROMDEVICE); } else { msg->fun = SSD_FUNC_WRITE; msg->flag = dev->wmode; pci_map_sg(dev->pdev, sgl, cmd->nsegs, PCI_DMA_TODEVICE); } sge = msg->sge; for (i=0; insegs; i++) { sge->block = block; sge->length = sg_dma_len(sgl) >> 9; sge->buf = sg_dma_address(sgl); block += sge->length; sgl++; sge++; } msg->tag = tag; #ifdef SSD_OT_PROTECT if (unlikely(dev->ot_delay > 0 && dev->ot_protect != 0)) { msleep_interruptible(dev->ot_delay); } #endif ssd_start_io_acct(cmd); dev->scmd(cmd); return 0; } /* threads */ static int ssd_done_thread(void *data) { struct ssd_device *dev; struct bio *bio; struct bio *next; #ifdef SSD_ESCAPE_IRQ cpumask_t new_mask; #endif if (!data) { return -EINVAL; } dev = data; //set_user_nice(current, -5); while (!kthread_should_stop()) { wait_event_interruptible(dev->done_waitq, (atomic_read(&dev->in_doneq) || kthread_should_stop())); while (atomic_read(&dev->in_doneq)) { if (threaded_irq) { spin_lock(&dev->doneq_lock); bio = ssd_blist_get(&dev->doneq); spin_unlock(&dev->doneq_lock); } else { spin_lock_irq(&dev->doneq_lock); bio = ssd_blist_get(&dev->doneq); spin_unlock_irq(&dev->doneq_lock); } while (bio) { next = bio->bi_next; bio->bi_next = NULL; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, 0); #else bio_endio(bio, bio->bi_size, 0); #endif atomic_dec(&dev->in_doneq); bio = next; } cond_resched(); #ifdef SSD_ESCAPE_IRQ if (unlikely(smp_processor_id() == dev->irq_cpu)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)) cpumask_setall(&new_mask); cpumask_clear_cpu(dev->irq_cpu, &new_mask); set_cpus_allowed_ptr(current, &new_mask); #else cpus_setall(new_mask); cpu_clear(dev->irq_cpu, new_mask); set_cpus_allowed(current, new_mask); #endif } #endif } } return 0; } static int ssd_send_thread(void *data) { struct ssd_device *dev; struct bio *bio; struct bio *next; #ifdef SSD_ESCAPE_IRQ cpumask_t new_mask; #endif if (!data) { return -EINVAL; } dev = data; //set_user_nice(current, -5); while (!kthread_should_stop()) { wait_event_interruptible(dev->send_waitq, (atomic_read(&dev->in_sendq) || kthread_should_stop())); while (atomic_read(&dev->in_sendq)) { spin_lock(&dev->sendq_lock); bio = ssd_blist_get(&dev->sendq); spin_unlock(&dev->sendq_lock); while (bio) { next = bio->bi_next; bio->bi_next = NULL; #ifdef SSD_QUEUE_PBIO if (test_and_clear_bit(BIO_SSD_PBIO, &bio->bi_flags)) { __ssd_submit_pbio(dev, bio, 1); } else { ssd_submit_bio(dev, bio, 1); } #else ssd_submit_bio(dev, bio, 1); #endif atomic_dec(&dev->in_sendq); bio = next; } cond_resched(); #ifdef SSD_ESCAPE_IRQ if (unlikely(smp_processor_id() == dev->irq_cpu)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)) cpumask_setall(&new_mask); cpumask_clear_cpu(dev->irq_cpu, &new_mask); set_cpus_allowed_ptr(current, &new_mask); #else cpus_setall(new_mask); cpu_clear(dev->irq_cpu, new_mask); set_cpus_allowed(current, new_mask); #endif } #endif } } return 0; } static void ssd_cleanup_thread(struct ssd_device *dev) { kthread_stop(dev->send_thread); kthread_stop(dev->done_thread); } static int ssd_init_thread(struct ssd_device *dev) { int ret; atomic_set(&dev->in_doneq, 0); atomic_set(&dev->in_sendq, 0); spin_lock_init(&dev->doneq_lock); spin_lock_init(&dev->sendq_lock); ssd_blist_init(&dev->doneq); ssd_blist_init(&dev->sendq); init_waitqueue_head(&dev->done_waitq); init_waitqueue_head(&dev->send_waitq); dev->done_thread = kthread_run(ssd_done_thread, dev, "%s/d", dev->name); if (IS_ERR(dev->done_thread)) { ret = PTR_ERR(dev->done_thread); goto out_done_thread; } dev->send_thread = kthread_run(ssd_send_thread, dev, "%s/s", dev->name); if (IS_ERR(dev->send_thread)) { ret = PTR_ERR(dev->send_thread); goto out_send_thread; } return 0; out_send_thread: kthread_stop(dev->done_thread); out_done_thread: return ret; } /* dcmd pool */ static void ssd_put_dcmd(struct ssd_dcmd *dcmd) { struct ssd_device *dev = (struct ssd_device *)dcmd->dev; spin_lock(&dev->dcmd_lock); list_add_tail(&dcmd->list, &dev->dcmd_list); spin_unlock(&dev->dcmd_lock); } static struct ssd_dcmd *ssd_get_dcmd(struct ssd_device *dev) { struct ssd_dcmd *dcmd = NULL; spin_lock(&dev->dcmd_lock); if (!list_empty(&dev->dcmd_list)) { dcmd = list_entry(dev->dcmd_list.next, struct ssd_dcmd, list); list_del_init(&dcmd->list); } spin_unlock(&dev->dcmd_lock); return dcmd; } static void ssd_cleanup_dcmd(struct ssd_device *dev) { kfree(dev->dcmd); } static int ssd_init_dcmd(struct ssd_device *dev) { struct ssd_dcmd *dcmd; int dcmd_sz = sizeof(struct ssd_dcmd)*dev->hw_info.cmd_fifo_sz; int i; spin_lock_init(&dev->dcmd_lock); INIT_LIST_HEAD(&dev->dcmd_list); init_waitqueue_head(&dev->dcmd_wq); dev->dcmd = kmalloc(dcmd_sz, GFP_KERNEL); if (!dev->dcmd) { hio_warn("%s: can not alloc dcmd\n", dev->name); goto out_alloc_dcmd; } memset(dev->dcmd, 0, dcmd_sz); for (i=0, dcmd=dev->dcmd; i<(int)dev->hw_info.cmd_fifo_sz; i++, dcmd++) { dcmd->dev = dev; INIT_LIST_HEAD(&dcmd->list); list_add_tail(&dcmd->list, &dev->dcmd_list); } return 0; out_alloc_dcmd: return -ENOMEM; } static void ssd_put_dmsg(void *msg) { struct ssd_dcmd *dcmd = container_of(msg, struct ssd_dcmd, msg); struct ssd_device *dev = (struct ssd_device *)dcmd->dev; memset(dcmd->msg, 0, SSD_DCMD_MAX_SZ); ssd_put_dcmd(dcmd); wake_up(&dev->dcmd_wq); } static void *ssd_get_dmsg(struct ssd_device *dev) { struct ssd_dcmd *dcmd = ssd_get_dcmd(dev); while (!dcmd) { DEFINE_WAIT(wait); prepare_to_wait_exclusive(&dev->dcmd_wq, &wait, TASK_UNINTERRUPTIBLE); schedule(); dcmd = ssd_get_dcmd(dev); finish_wait(&dev->dcmd_wq, &wait); } return dcmd->msg; } /* do direct cmd */ static int ssd_do_request(struct ssd_device *dev, int rw, void *msg, int *done) { DECLARE_COMPLETION(wait); struct ssd_cmd *cmd; int tag; int ret = 0; tag = ssd_get_tag(dev, 1); if (tag < 0) { return -EBUSY; } cmd = &dev->cmd[tag]; cmd->nsegs = 1; memcpy(cmd->msg, msg, SSD_DCMD_MAX_SZ); ((struct ssd_rw_msg *)cmd->msg)->tag = tag; cmd->waiting = &wait; dev->scmd(cmd); wait_for_completion(cmd->waiting); cmd->waiting = NULL; if (cmd->errors == -ETIMEDOUT) { ret = cmd->errors; } else if (cmd->errors) { ret = -EIO; } if (done != NULL) { *done = cmd->nr_log; } ssd_put_tag(dev, cmd->tag); return ret; } static int ssd_do_barrier_request(struct ssd_device *dev, int rw, void *msg, int *done) { DECLARE_COMPLETION(wait); struct ssd_cmd *cmd; int tag; int ret = 0; tag = ssd_barrier_get_tag(dev); if (tag < 0) { return -EBUSY; } cmd = &dev->cmd[tag]; cmd->nsegs = 1; memcpy(cmd->msg, msg, SSD_DCMD_MAX_SZ); ((struct ssd_rw_msg *)cmd->msg)->tag = tag; cmd->waiting = &wait; dev->scmd(cmd); wait_for_completion(cmd->waiting); cmd->waiting = NULL; if (cmd->errors == -ETIMEDOUT) { ret = cmd->errors; } else if (cmd->errors) { ret = -EIO; } if (done != NULL) { *done = cmd->nr_log; } ssd_barrier_put_tag(dev, cmd->tag); return ret; } #ifdef SSD_OT_PROTECT static void ssd_check_temperature(struct ssd_device *dev, int temp) { uint64_t val; uint32_t off; int cur; int i; if (mode != SSD_DRV_MODE_STANDARD) { return; } if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { } for (i=0; ihw_info.nr_ctrl; i++) { off = SSD_CTRL_TEMP_REG0 + i * sizeof(uint64_t); val = ssd_reg_read(dev->ctrlp + off); if (val == 0xffffffffffffffffull) { continue; } cur = (int)CUR_TEMP(val); if (cur >= temp) { if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_CTRL), &dev->hwmon)) { if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2_2) { hio_warn("%s: Over temperature, please check the fans.\n", dev->name); dev->ot_delay = SSD_OT_DELAY; } } return; } } if (test_and_clear_bit(SSD_HWMON_TEMP(SSD_TEMP_CTRL), &dev->hwmon)) { if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2_2) { hio_warn("%s: Temperature is OK.\n", dev->name); dev->ot_delay = 0; } } } #endif static int ssd_get_ot_status(struct ssd_device *dev, int *status) { uint32_t off; uint32_t val; int i; if (!dev || !status) { return -EINVAL; } if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2_2) { for (i=0; ihw_info.nr_ctrl; i++) { off = SSD_READ_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ); val = ssd_reg32_read(dev->ctrlp + off); if ((val >> 22) & 0x1) { *status = 1; goto out; } off = SSD_WRITE_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ); val = ssd_reg32_read(dev->ctrlp + off); if ((val >> 22) & 0x1) { *status = 1; goto out; } } } else { *status = !!dev->ot_delay; } out: return 0; } static void ssd_set_ot_protect(struct ssd_device *dev, int protect) { uint32_t off; uint32_t val; int i; mutex_lock(&dev->fw_mutex); dev->ot_protect = !!protect; if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2_2) { for (i=0; ihw_info.nr_ctrl; i++) { off = SSD_READ_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ); val = ssd_reg32_read(dev->ctrlp + off); if (dev->ot_protect) { val |= (1U << 21); } else { val &= ~(1U << 21); } ssd_reg32_write(dev->ctrlp + off, val); off = SSD_WRITE_OT_REG0 + (i * SSD_CTRL_REG_ZONE_SZ); val = ssd_reg32_read(dev->ctrlp + off); if (dev->ot_protect) { val |= (1U << 21); } else { val &= ~(1U << 21); } ssd_reg32_write(dev->ctrlp + off, val); } } mutex_unlock(&dev->fw_mutex); } static int ssd_init_ot_protect(struct ssd_device *dev) { ssd_set_ot_protect(dev, ot_protect); #ifdef SSD_OT_PROTECT ssd_check_temperature(dev, SSD_OT_TEMP); #endif return 0; } /* log */ static int ssd_read_log(struct ssd_device *dev, int ctrl_idx, void *buf, int *nr_log) { struct ssd_log_op_msg *msg; struct ssd_log_msg *lmsg; dma_addr_t buf_dma; size_t length = dev->hw_info.log_sz; int ret = 0; if (ctrl_idx >= dev->hw_info.nr_ctrl) { return -EINVAL; } buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map read DMA buffer\n", dev->name); goto out_dma_mapping; } msg = (struct ssd_log_op_msg *)ssd_get_dmsg(dev); if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { lmsg = (struct ssd_log_msg *)msg; lmsg->fun = SSD_FUNC_READ_LOG; lmsg->ctrl_idx = ctrl_idx; lmsg->buf = buf_dma; } else { msg->fun = SSD_FUNC_READ_LOG; msg->ctrl_idx = ctrl_idx; msg->buf = buf_dma; } ret = ssd_do_request(dev, READ, msg, nr_log); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE); out_dma_mapping: return ret; } #define SSD_LOG_PRINT_BUF_SZ 256 static int ssd_parse_log(struct ssd_device *dev, struct ssd_log *log, int print) { struct ssd_log_desc *log_desc = ssd_log_desc; struct ssd_log_entry *le; char *sn = NULL; char print_buf[SSD_LOG_PRINT_BUF_SZ]; int print_len; le = &log->le; /* find desc */ while (log_desc->event != SSD_UNKNOWN_EVENT) { if (log_desc->event == le->event) { break; } log_desc++; } if (!print) { goto out; } if (log_desc->level < log_level) { goto out; } /* parse */ if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { sn = dev->label.sn; } else { sn = dev->labelv3.barcode; } print_len = snprintf(print_buf, SSD_LOG_PRINT_BUF_SZ, "%s (%s): <%#x>", dev->name, sn, le->event); if (log->ctrl_idx != SSD_LOG_SW_IDX) { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " controller %d", log->ctrl_idx); } switch (log_desc->data) { case SSD_LOG_DATA_NONE: break; case SSD_LOG_DATA_LOC: if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " flash %d", le->data.loc.flash); if (log_desc->sblock) { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " block %d", le->data.loc.block); } if (log_desc->spage) { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " page %d", le->data.loc.page); } } else { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " flash %d", le->data.loc1.flash); if (log_desc->sblock) { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " block %d", le->data.loc1.block); } if (log_desc->spage) { print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " page %d", le->data.loc1.page); } } break; case SSD_LOG_DATA_HEX: print_len += snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), " info %#x", le->data.val); break; default: break; } /*print_len += */snprintf((print_buf + print_len), (SSD_LOG_PRINT_BUF_SZ - print_len), ": %s", log_desc->desc); switch (log_desc->level) { case SSD_LOG_LEVEL_INFO: hio_info("%s\n", print_buf); break; case SSD_LOG_LEVEL_NOTICE: hio_note("%s\n", print_buf); break; case SSD_LOG_LEVEL_WARNING: hio_warn("%s\n", print_buf); break; case SSD_LOG_LEVEL_ERR: hio_err("%s\n", print_buf); //printk(KERN_ERR MODULE_NAME": some exception occurred, please check the data or refer to FAQ."); break; default: hio_warn("%s\n", print_buf); break; } out: return log_desc->level; } static int ssd_bm_get_sfstatus(struct ssd_device *dev, uint16_t *status); static int ssd_switch_wmode(struct ssd_device *dev, int wmode); static int ssd_handle_event(struct ssd_device *dev, uint16_t event, int level) { int ret = 0; switch (event) { case SSD_LOG_OVER_TEMP: { #ifdef SSD_OT_PROTECT if (!test_and_set_bit(SSD_HWMON_TEMP(SSD_TEMP_CTRL), &dev->hwmon)) { if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2_2) { hio_warn("%s: Over temperature, please check the fans.\n", dev->name); dev->ot_delay = SSD_OT_DELAY; } } #endif break; } case SSD_LOG_NORMAL_TEMP: { #ifdef SSD_OT_PROTECT /* need to check all controller's temperature */ ssd_check_temperature(dev, SSD_OT_TEMP_HYST); #endif break; } case SSD_LOG_BATTERY_FAULT: { uint16_t sfstatus; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { if (!ssd_bm_get_sfstatus(dev, &sfstatus)) { ssd_gen_swlog(dev, SSD_LOG_BM_SFSTATUS, sfstatus); } } if (!test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { ssd_switch_wmode(dev, dev->user_wmode); } break; } case SSD_LOG_BATTERY_OK: { if (test_and_clear_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { ssd_switch_wmode(dev, dev->user_wmode); } break; } case SSD_LOG_BOARD_VOLT_FAULT: { ssd_mon_boardvolt(dev); break; } case SSD_LOG_CLEAR_LOG: { /* update smart */ memset(&dev->smart.log_info, 0, sizeof(struct ssd_log_info)); break; } case SSD_LOG_CAP_VOLT_FAULT: case SSD_LOG_CAP_LEARN_FAULT: case SSD_LOG_CAP_SHORT_CIRCUIT: { if (!test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { ssd_switch_wmode(dev, dev->user_wmode); } break; } default: break; } /* ssd event call */ if (dev->event_call) { dev->event_call(dev->gd, event, level); /* FIXME */ if (SSD_LOG_CAP_VOLT_FAULT == event || SSD_LOG_CAP_LEARN_FAULT == event || SSD_LOG_CAP_SHORT_CIRCUIT == event) { dev->event_call(dev->gd, SSD_LOG_BATTERY_FAULT, level); } } return ret; } static int ssd_save_log(struct ssd_device *dev, struct ssd_log *log) { uint32_t off, size; void *internal_log; int ret = 0; mutex_lock(&dev->internal_log_mutex); size = sizeof(struct ssd_log); off = dev->internal_log.nr_log * size; if (off == dev->rom_info.log_sz) { if (dev->internal_log.nr_log == dev->smart.log_info.nr_log) { hio_warn("%s: internal log is full\n", dev->name); } goto out; } internal_log = dev->internal_log.log + off; memcpy(internal_log, log, size); if (dev->protocol_info.ver > SSD_PROTOCOL_V3) { off += dev->rom_info.log_base; ret = ssd_spi_write(dev, log, off, size); if (ret) { goto out; } } dev->internal_log.nr_log++; out: mutex_unlock(&dev->internal_log_mutex); return ret; } static int ssd_save_swlog(struct ssd_device *dev, uint16_t event, uint32_t data) { struct ssd_log log; struct timeval tv; int level; int ret = 0; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; memset(&log, 0, sizeof(struct ssd_log)); do_gettimeofday(&tv); log.ctrl_idx = SSD_LOG_SW_IDX; log.time = tv.tv_sec; log.le.event = event; log.le.data.val = data; level = ssd_parse_log(dev, &log, 0); if (level >= SSD_LOG_LEVEL) { ret = ssd_save_log(dev, &log); } /* set alarm */ if (SSD_LOG_LEVEL_ERR == level) { ssd_set_alarm(dev); } /* update smart */ dev->smart.log_info.nr_log++; dev->smart.log_info.stat[level]++; /* handle event */ ssd_handle_event(dev, event, level); return ret; } static int ssd_gen_swlog(struct ssd_device *dev, uint16_t event, uint32_t data) { struct ssd_log_entry le; int ret; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; /* slave port ? */ if (dev->slave) { return 0; } memset(&le, 0, sizeof(struct ssd_log_entry)); le.event = event; le.data.val = data; ret = sfifo_put(&dev->log_fifo, &le); if (ret) { return ret; } if (test_bit(SSD_INIT_WORKQ, &dev->state)) { queue_work(dev->workq, &dev->log_work); } return 0; } static int ssd_do_swlog(struct ssd_device *dev) { struct ssd_log_entry le; int ret = 0; memset(&le, 0, sizeof(struct ssd_log_entry)); while (!sfifo_get(&dev->log_fifo, &le)) { ret = ssd_save_swlog(dev, le.event, le.data.val); if (ret) { break; } } return ret; } static int __ssd_clear_log(struct ssd_device *dev) { uint32_t off, length; int ret; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } if (dev->internal_log.nr_log == 0) { return 0; } mutex_lock(&dev->internal_log_mutex); off = dev->rom_info.log_base; length = dev->rom_info.log_sz; ret = ssd_spi_erase(dev, off, length); if (ret) { hio_warn("%s: log erase: failed\n", dev->name); goto out; } dev->internal_log.nr_log = 0; out: mutex_unlock(&dev->internal_log_mutex); return ret; } static int ssd_clear_log(struct ssd_device *dev) { int ret; ret = __ssd_clear_log(dev); if(!ret) { ssd_gen_swlog(dev, SSD_LOG_CLEAR_LOG, 0); } return ret; } static int ssd_do_log(struct ssd_device *dev, int ctrl_idx, void *buf) { struct ssd_log_entry *le; struct ssd_log log; struct timeval tv; int nr_log = 0; int level; int ret = 0; ret = ssd_read_log(dev, ctrl_idx, buf, &nr_log); if (ret) { return ret; } do_gettimeofday(&tv); log.time = tv.tv_sec; log.ctrl_idx = ctrl_idx; le = (ssd_log_entry_t *)buf; while (nr_log > 0) { memcpy(&log.le, le, sizeof(struct ssd_log_entry)); level = ssd_parse_log(dev, &log, 1); if (level >= SSD_LOG_LEVEL) { ssd_save_log(dev, &log); } /* set alarm */ if (SSD_LOG_LEVEL_ERR == level) { ssd_set_alarm(dev); } dev->smart.log_info.nr_log++; if (SSD_LOG_SEU_FAULT != le->event && SSD_LOG_SEU_FAULT1 != le->event) { dev->smart.log_info.stat[level]++; } else { /* SEU fault */ /* log to the volatile log info */ dev->log_info.nr_log++; dev->log_info.stat[level]++; /* do something */ dev->reload_fw = 1; ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FLAG); /*dev->readonly = 1; set_disk_ro(dev->gd, 1); hio_warn("%s: switched to read-only mode.\n", dev->name);*/ } /* handle event */ ssd_handle_event(dev, le->event, level); le++; nr_log--; } return 0; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) static void ssd_log_worker(void *data) { struct ssd_device *dev = (struct ssd_device *)data; #else static void ssd_log_worker(struct work_struct *work) { struct ssd_device *dev = container_of(work, struct ssd_device, log_work); #endif int i; int ret; if (!test_bit(SSD_LOG_ERR, &dev->state) && test_bit(SSD_ONLINE, &dev->state)) { /* alloc log buf */ if (!dev->log_buf) { dev->log_buf = kmalloc(dev->hw_info.log_sz, GFP_KERNEL); if (!dev->log_buf) { hio_warn("%s: ssd_log_worker: no mem\n", dev->name); return; } } /* get log */ if (test_and_clear_bit(SSD_LOG_HW, &dev->state)) { for (i=0; ihw_info.nr_ctrl; i++) { ret = ssd_do_log(dev, i, dev->log_buf); if (ret) { (void)test_and_set_bit(SSD_LOG_ERR, &dev->state); hio_warn("%s: do log fail\n", dev->name); } } } } ret = ssd_do_swlog(dev); if (ret) { hio_warn("%s: do swlog fail\n", dev->name); } } static void ssd_cleanup_log(struct ssd_device *dev) { if (dev->log_buf) { kfree(dev->log_buf); dev->log_buf = NULL; } sfifo_free(&dev->log_fifo); if (dev->internal_log.log) { vfree(dev->internal_log.log); dev->internal_log.log = NULL; } } static int ssd_init_log(struct ssd_device *dev) { struct ssd_log *log; uint32_t off, size; uint32_t len = 0; int ret = 0; mutex_init(&dev->internal_log_mutex); #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) INIT_WORK(&dev->log_work, ssd_log_worker, dev); #else INIT_WORK(&dev->log_work, ssd_log_worker); #endif off = dev->rom_info.log_base; size = dev->rom_info.log_sz; dev->internal_log.log = vmalloc(size); if (!dev->internal_log.log) { ret = -ENOMEM; goto out_alloc_log; } ret = sfifo_alloc(&dev->log_fifo, SSD_LOG_FIFO_SZ, sizeof(struct ssd_log_entry)); if (ret < 0) { goto out_alloc_log_fifo; } if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } log = (struct ssd_log *)dev->internal_log.log; while (len < size) { ret = ssd_spi_read(dev, log, off, sizeof(struct ssd_log)); if (ret) { goto out_read_log; } if (log->ctrl_idx == 0xff) { break; } dev->internal_log.nr_log++; log++; len += sizeof(struct ssd_log); off += sizeof(struct ssd_log); } return 0; out_read_log: sfifo_free(&dev->log_fifo); out_alloc_log_fifo: vfree(dev->internal_log.log); dev->internal_log.log = NULL; dev->internal_log.nr_log = 0; out_alloc_log: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } /* work queue */ static void ssd_stop_workq(struct ssd_device *dev) { test_and_clear_bit(SSD_INIT_WORKQ, &dev->state); flush_workqueue(dev->workq); } static void ssd_start_workq(struct ssd_device *dev) { (void)test_and_set_bit(SSD_INIT_WORKQ, &dev->state); /* log ? */ queue_work(dev->workq, &dev->log_work); } static void ssd_cleanup_workq(struct ssd_device *dev) { flush_workqueue(dev->workq); destroy_workqueue(dev->workq); dev->workq = NULL; } static int ssd_init_workq(struct ssd_device *dev) { int ret = 0; dev->workq = create_singlethread_workqueue(dev->name); if (!dev->workq) { ret = -ESRCH; goto out; } out: return ret; } /* rom */ static int ssd_init_rom_info(struct ssd_device *dev) { uint32_t val; mutex_init(&dev->spi_mutex); mutex_init(&dev->i2c_mutex); if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { /* fix bug: read data to clear status */ (void)ssd_reg32_read(dev->ctrlp + SSD_SPI_REG_RDATA); dev->rom_info.size = SSD_ROM_SIZE; dev->rom_info.block_size = SSD_ROM_BLK_SIZE; dev->rom_info.page_size = SSD_ROM_PAGE_SIZE; dev->rom_info.bridge_fw_base = SSD_ROM_BRIDGE_FW_BASE; dev->rom_info.bridge_fw_sz = SSD_ROM_BRIDGE_FW_SIZE; dev->rom_info.nr_bridge_fw = SSD_ROM_NR_BRIDGE_FW; dev->rom_info.ctrl_fw_base = SSD_ROM_CTRL_FW_BASE; dev->rom_info.ctrl_fw_sz = SSD_ROM_CTRL_FW_SIZE; dev->rom_info.nr_ctrl_fw = SSD_ROM_NR_CTRL_FW; dev->rom_info.log_sz = SSD_ROM_LOG_SZ; dev->rom_info.vp_base = SSD_ROM_VP_BASE; dev->rom_info.label_base = SSD_ROM_LABEL_BASE; } else if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { val = ssd_reg32_read(dev->ctrlp + SSD_ROM_INFO_REG); dev->rom_info.size = 0x100000 * (1U << (val & 0xFF)); dev->rom_info.block_size = 0x10000 * (1U << ((val>>8) & 0xFF)); dev->rom_info.page_size = (val>>16) & 0xFFFF; val = ssd_reg32_read(dev->ctrlp + SSD_ROM_BRIDGE_FW_INFO_REG); dev->rom_info.bridge_fw_base = dev->rom_info.block_size * (val & 0xFFFF); dev->rom_info.bridge_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF); dev->rom_info.nr_bridge_fw = ((val >> 30) & 0x3) + 1; val = ssd_reg32_read(dev->ctrlp + SSD_ROM_CTRL_FW_INFO_REG); dev->rom_info.ctrl_fw_base = dev->rom_info.block_size * (val & 0xFFFF); dev->rom_info.ctrl_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF); dev->rom_info.nr_ctrl_fw = ((val >> 30) & 0x3) + 1; dev->rom_info.bm_fw_base = dev->rom_info.ctrl_fw_base + (dev->rom_info.nr_ctrl_fw * dev->rom_info.ctrl_fw_sz); dev->rom_info.bm_fw_sz = SSD_PV3_ROM_BM_FW_SZ; dev->rom_info.nr_bm_fw = SSD_PV3_ROM_NR_BM_FW; dev->rom_info.log_base = dev->rom_info.bm_fw_base + (dev->rom_info.nr_bm_fw * dev->rom_info.bm_fw_sz); dev->rom_info.log_sz = SSD_ROM_LOG_SZ; dev->rom_info.smart_base = dev->rom_info.log_base + dev->rom_info.log_sz; dev->rom_info.smart_sz = SSD_PV3_ROM_SMART_SZ; dev->rom_info.nr_smart = SSD_PV3_ROM_NR_SMART; val = ssd_reg32_read(dev->ctrlp + SSD_ROM_VP_INFO_REG); dev->rom_info.vp_base = dev->rom_info.block_size * val; dev->rom_info.label_base = dev->rom_info.vp_base + dev->rom_info.block_size; if (dev->rom_info.label_base >= dev->rom_info.size) { dev->rom_info.label_base = dev->rom_info.vp_base - dev->rom_info.block_size; } } else { val = ssd_reg32_read(dev->ctrlp + SSD_ROM_INFO_REG); dev->rom_info.size = 0x100000 * (1U << (val & 0xFF)); dev->rom_info.block_size = 0x10000 * (1U << ((val>>8) & 0xFF)); dev->rom_info.page_size = (val>>16) & 0xFFFF; val = ssd_reg32_read(dev->ctrlp + SSD_ROM_BRIDGE_FW_INFO_REG); dev->rom_info.bridge_fw_base = dev->rom_info.block_size * (val & 0xFFFF); dev->rom_info.bridge_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF); dev->rom_info.nr_bridge_fw = ((val >> 30) & 0x3) + 1; val = ssd_reg32_read(dev->ctrlp + SSD_ROM_CTRL_FW_INFO_REG); dev->rom_info.ctrl_fw_base = dev->rom_info.block_size * (val & 0xFFFF); dev->rom_info.ctrl_fw_sz = dev->rom_info.block_size * ((val>>16) & 0x3FFF); dev->rom_info.nr_ctrl_fw = ((val >> 30) & 0x3) + 1; val = ssd_reg32_read(dev->ctrlp + SSD_ROM_VP_INFO_REG); dev->rom_info.vp_base = dev->rom_info.block_size * val; dev->rom_info.label_base = dev->rom_info.vp_base - SSD_PV3_2_ROM_SEC_SZ; dev->rom_info.nr_smart = SSD_PV3_ROM_NR_SMART; dev->rom_info.smart_sz = SSD_PV3_2_ROM_SEC_SZ; dev->rom_info.smart_base = dev->rom_info.label_base - (dev->rom_info.smart_sz * dev->rom_info.nr_smart); if (dev->rom_info.smart_sz > dev->rom_info.block_size) { dev->rom_info.smart_sz = dev->rom_info.block_size; } dev->rom_info.log_sz = SSD_PV3_2_ROM_LOG_SZ; dev->rom_info.log_base = dev->rom_info.smart_base - dev->rom_info.log_sz; } return ssd_init_spi(dev); } /* smart */ static int ssd_update_smart(struct ssd_device *dev, struct ssd_smart *smart) { struct timeval tv; uint64_t run_time; #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27)) struct hd_struct *part; int cpu; #endif int i, j; int ret = 0; if (!test_bit(SSD_INIT_BD, &dev->state)) { return 0; } do_gettimeofday(&tv); if ((uint64_t)tv.tv_sec < dev->uptime) { run_time = 0; } else { run_time = tv.tv_sec - dev->uptime; } /* avoid frequently update */ if (run_time >= 60) { ret = 1; } /* io stat */ smart->io_stat.run_time += run_time; #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27)) cpu = part_stat_lock(); part = &dev->gd->part0; part_round_stats(cpu, part); part_stat_unlock(); smart->io_stat.nr_read += part_stat_read(part, ios[READ]); smart->io_stat.nr_write += part_stat_read(part, ios[WRITE]); smart->io_stat.rsectors += part_stat_read(part, sectors[READ]); smart->io_stat.wsectors += part_stat_read(part, sectors[WRITE]); #elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14)) preempt_disable(); disk_round_stats(dev->gd); preempt_enable(); smart->io_stat.nr_read += disk_stat_read(dev->gd, ios[READ]); smart->io_stat.nr_write += disk_stat_read(dev->gd, ios[WRITE]); smart->io_stat.rsectors += disk_stat_read(dev->gd, sectors[READ]); smart->io_stat.wsectors += disk_stat_read(dev->gd, sectors[WRITE]); #else preempt_disable(); disk_round_stats(dev->gd); preempt_enable(); smart->io_stat.nr_read += disk_stat_read(dev->gd, reads); smart->io_stat.nr_write += disk_stat_read(dev->gd, writes); smart->io_stat.rsectors += disk_stat_read(dev->gd, read_sectors); smart->io_stat.wsectors += disk_stat_read(dev->gd, write_sectors); #endif smart->io_stat.nr_to += atomic_read(&dev->tocnt); for (i=0; inr_queue; i++) { smart->io_stat.nr_rwerr += dev->queue[i].io_stat.nr_rwerr; smart->io_stat.nr_ioerr += dev->queue[i].io_stat.nr_ioerr; } for (i=0; inr_queue; i++) { for (j=0; jecc_info.bitflip[j] += dev->queue[i].ecc_info.bitflip[j]; } } //dev->uptime = tv.tv_sec; return ret; } static int ssd_clear_smart(struct ssd_device *dev) { struct timeval tv; uint64_t sversion; uint32_t off, length; int i; int ret; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } /* clear smart */ off = dev->rom_info.smart_base; length = dev->rom_info.smart_sz * dev->rom_info.nr_smart; ret = ssd_spi_erase(dev, off, length); if (ret) { hio_warn("%s: info erase: failed\n", dev->name); goto out; } sversion = dev->smart.version; memset(&dev->smart, 0, sizeof(struct ssd_smart)); dev->smart.version = sversion + 1; dev->smart.magic = SSD_SMART_MAGIC; /* clear all tmp acc */ for (i=0; inr_queue; i++) { memset(&(dev->queue[i].io_stat), 0, sizeof(struct ssd_io_stat)); memset(&(dev->queue[i].ecc_info), 0, sizeof(struct ssd_ecc_info)); } atomic_set(&dev->tocnt, 0); /* clear tmp log info */ memset(&dev->log_info, 0, sizeof(struct ssd_log_info)); do_gettimeofday(&tv); dev->uptime = tv.tv_sec; /* clear alarm ? */ //ssd_clear_alarm(dev); out: return ret; } static int ssd_save_smart(struct ssd_device *dev) { uint32_t off, size; int i; int ret = 0; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } if (!ssd_update_smart(dev, &dev->smart)) { return 0; } dev->smart.version++; for (i=0; irom_info.nr_smart; i++) { off = dev->rom_info.smart_base + (dev->rom_info.smart_sz * i); size = dev->rom_info.smart_sz; ret = ssd_spi_erase(dev, off, size); if (ret) { hio_warn("%s: info erase failed\n", dev->name); goto out; } size = sizeof(struct ssd_smart); ret = ssd_spi_write(dev, &dev->smart, off, size); if (ret) { hio_warn("%s: info write failed\n", dev->name); goto out; } //xx } out: return ret; } static int ssd_init_smart(struct ssd_device *dev) { struct ssd_smart *smart; struct timeval tv; uint32_t off, size; int i; int ret = 0; do_gettimeofday(&tv); dev->uptime = tv.tv_sec; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } smart = kmalloc(sizeof(struct ssd_smart) * SSD_ROM_NR_SMART_MAX, GFP_KERNEL); if (!smart) { ret = -ENOMEM; goto out_nomem; } memset(&dev->smart, 0, sizeof(struct ssd_smart)); /* read smart */ for (i=0; irom_info.nr_smart; i++) { memset(&smart[i], 0, sizeof(struct ssd_smart)); off = dev->rom_info.smart_base + (dev->rom_info.smart_sz * i); size = sizeof(struct ssd_smart); ret = ssd_spi_read(dev, &smart[i], off, size); if (ret) { hio_warn("%s: info read failed\n", dev->name); goto out; } if (smart[i].magic != SSD_SMART_MAGIC) { smart[i].magic = 0; smart[i].version = 0; continue; } if (smart[i].version > dev->smart.version) { memcpy(&dev->smart, &smart[i], sizeof(struct ssd_smart)); } } if (dev->smart.magic != SSD_SMART_MAGIC) { /* first time power up */ dev->smart.magic = SSD_SMART_MAGIC; dev->smart.version = 1; } /* check log info */ { struct ssd_log_info log_info; struct ssd_log *log = (struct ssd_log *)dev->internal_log.log; memset(&log_info, 0, sizeof(struct ssd_log_info)); while (log_info.nr_log < dev->internal_log.nr_log) { /* skip the volatile log info */ if (SSD_LOG_SEU_FAULT != log->le.event && SSD_LOG_SEU_FAULT1 != log->le.event) { log_info.stat[ssd_parse_log(dev, log, 0)]++; } log_info.nr_log++; log++; } /* check */ for (i=(SSD_LOG_NR_LEVEL-1); i>=0; i--) { if (log_info.stat[i] > dev->smart.log_info.stat[i]) { /* unclean */ memcpy(&dev->smart.log_info, &log_info, sizeof(struct ssd_log_info)); dev->smart.version++; break; } } } for (i=0; irom_info.nr_smart; i++) { if (smart[i].magic == SSD_SMART_MAGIC && smart[i].version == dev->smart.version) { continue; } off = dev->rom_info.smart_base + (dev->rom_info.smart_sz * i); size = dev->rom_info.smart_sz; ret = ssd_spi_erase(dev, off, size); if (ret) { hio_warn("%s: info erase failed\n", dev->name); goto out; } size = sizeof(struct ssd_smart); ret = ssd_spi_write(dev, &dev->smart, off, size); if (ret) { hio_warn("%s: info write failed\n", dev->name); goto out; } //xx } /* sync smart with alarm led */ if (dev->smart.io_stat.nr_to || dev->smart.io_stat.nr_rwerr || dev->smart.log_info.stat[SSD_LOG_LEVEL_ERR]) { hio_warn("%s: some fault found in the history info\n", dev->name); ssd_set_alarm(dev); } out: kfree(smart); out_nomem: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } /* bm */ static int __ssd_bm_get_version(struct ssd_device *dev, uint16_t *ver) { struct ssd_bm_manufacturer_data bm_md = {0}; uint16_t sc_id = SSD_BM_SYSTEM_DATA_SUBCLASS_ID; uint8_t cmd; int ret = 0; if (!dev || !ver) { return -EINVAL; } mutex_lock(&dev->bm_mutex); cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID; ret = ssd_smbus_write_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&sc_id); if (ret) { goto out; } cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID_PAGE1; ret = ssd_smbus_read_block(dev, SSD_BM_SLAVE_ADDRESS, cmd, sizeof(struct ssd_bm_manufacturer_data), (uint8_t *)&bm_md); if (ret) { goto out; } if (bm_md.firmware_ver & 0xF000) { ret = -EIO; goto out; } *ver = bm_md.firmware_ver; out: mutex_unlock(&dev->bm_mutex); return ret; } static int ssd_bm_get_version(struct ssd_device *dev, uint16_t *ver) { uint16_t tmp = 0; int i = SSD_BM_RETRY_MAX; int ret = 0; while (i-- > 0) { ret = __ssd_bm_get_version(dev, &tmp); if (!ret) { break; } } if (ret) { return ret; } *ver = tmp; return 0; } static int __ssd_bm_nr_cap(struct ssd_device *dev, int *nr_cap) { struct ssd_bm_configuration_registers bm_cr; uint16_t sc_id = SSD_BM_CONFIGURATION_REGISTERS_ID; uint8_t cmd; int ret; mutex_lock(&dev->bm_mutex); cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID; ret = ssd_smbus_write_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&sc_id); if (ret) { goto out; } cmd = SSD_BM_DATA_FLASH_SUBCLASS_ID_PAGE1; ret = ssd_smbus_read_block(dev, SSD_BM_SLAVE_ADDRESS, cmd, sizeof(struct ssd_bm_configuration_registers), (uint8_t *)&bm_cr); if (ret) { goto out; } if (bm_cr.operation_cfg.cc == 0 || bm_cr.operation_cfg.cc > 4) { ret = -EIO; goto out; } *nr_cap = bm_cr.operation_cfg.cc + 1; out: mutex_unlock(&dev->bm_mutex); return ret; } static int ssd_bm_nr_cap(struct ssd_device *dev, int *nr_cap) { int tmp = 0; int i = SSD_BM_RETRY_MAX; int ret = 0; while (i-- > 0) { ret = __ssd_bm_nr_cap(dev, &tmp); if (!ret) { break; } } if (ret) { return ret; } *nr_cap = tmp; return 0; } static int ssd_bm_enter_cap_learning(struct ssd_device *dev) { uint16_t buf = SSD_BM_ENTER_CAP_LEARNING; uint8_t cmd = SSD_BM_MANUFACTURERACCESS; int ret; ret = ssd_smbus_write_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&buf); if (ret) { goto out; } out: return ret; } static int ssd_bm_get_sfstatus(struct ssd_device *dev, uint16_t *status) { uint16_t val = 0; uint8_t cmd = SSD_BM_SAFETYSTATUS; int ret; ret = ssd_smbus_read_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&val); if (ret) { goto out; } *status = val; out: return ret; } static int ssd_bm_get_opstatus(struct ssd_device *dev, uint16_t *status) { uint16_t val = 0; uint8_t cmd = SSD_BM_OPERATIONSTATUS; int ret; ret = ssd_smbus_read_word(dev, SSD_BM_SLAVE_ADDRESS, cmd, (uint8_t *)&val); if (ret) { goto out; } *status = val; out: return ret; } static int ssd_get_bmstruct(struct ssd_device *dev, struct ssd_bm *bm_status_out) { struct sbs_cmd *bm_sbs = ssd_bm_sbs; struct ssd_bm bm_status; uint8_t buf[2] = {0, }; uint16_t val = 0; uint16_t cval; int ret = 0; memset(&bm_status, 0, sizeof(struct ssd_bm)); while (bm_sbs->desc != NULL) { switch (bm_sbs->size) { case SBS_SIZE_BYTE: ret = ssd_smbus_read_byte(dev, SSD_BM_SLAVE_ADDRESS, bm_sbs->cmd, buf); if (ret) { //printf("Error: smbus read byte %#x\n", bm_sbs->cmd); goto out; } val = buf[0]; break; case SBS_SIZE_WORD: ret = ssd_smbus_read_word(dev, SSD_BM_SLAVE_ADDRESS, bm_sbs->cmd, (uint8_t *)&val); if (ret) { //printf("Error: smbus read word %#x\n", bm_sbs->cmd); goto out; } //val = *(uint16_t *)buf; break; default: ret = -1; goto out; break; } switch (bm_sbs->unit) { case SBS_UNIT_VALUE: *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val & bm_sbs->mask; break; case SBS_UNIT_TEMPERATURE: cval = (uint16_t)(val - 2731) / 10; *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = cval; break; case SBS_UNIT_VOLTAGE: *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val; break; case SBS_UNIT_CURRENT: *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val; break; case SBS_UNIT_ESR: *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val; break; case SBS_UNIT_PERCENT: *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val; break; case SBS_UNIT_CAPACITANCE: *(uint16_t *)bm_var(&bm_status, bm_sbs->off) = val; break; default: ret = -1; goto out; break; } bm_sbs++; } memcpy(bm_status_out, &bm_status, sizeof(struct ssd_bm)); out: return ret; } static int __ssd_bm_status(struct ssd_device *dev, int *status) { struct ssd_bm bm_status = {0}; int nr_cap = 0; int i; int ret = 0; ret = ssd_get_bmstruct(dev, &bm_status); if (ret) { goto out; } /* capacitor voltage */ ret = ssd_bm_nr_cap(dev, &nr_cap); if (ret) { goto out; } for (i=0; i> 12) & 0x1)) { *status = SSD_BMSTATUS_CHARGING; }else{ *status = SSD_BMSTATUS_OK; } out: return ret; } static void ssd_set_flush_timeout(struct ssd_device *dev, int mode); #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) static void ssd_bm_worker(void *data) { struct ssd_device *dev = (struct ssd_device *)data; #else static void ssd_bm_worker(struct work_struct *work) { struct ssd_device *dev = container_of(work, struct ssd_device, bm_work); #endif uint16_t opstatus; int ret = 0; if (mode != SSD_DRV_MODE_STANDARD) { return; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) { return; } if (dev->hw_info_ext.plp_type != SSD_PLP_SCAP) { return; } ret = ssd_bm_get_opstatus(dev, &opstatus); if (ret) { hio_warn("%s: get bm operationstatus failed\n", dev->name); return; } /* need cap learning ? */ if (!(opstatus & 0xF0)) { ret = ssd_bm_enter_cap_learning(dev); if (ret) { hio_warn("%s: enter capacitance learning failed\n", dev->name); return; } } } static void ssd_bm_routine_start(void *data) { struct ssd_device *dev; if (!data) { return; } dev = data; if (test_bit(SSD_INIT_WORKQ, &dev->state)) { if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { queue_work(dev->workq, &dev->bm_work); } else { queue_work(dev->workq, &dev->capmon_work); } } } /* CAP */ static int ssd_do_cap_learn(struct ssd_device *dev, uint32_t *cap) { uint32_t u1, u2, t; uint16_t val = 0; int wait = 0; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { *cap = 0; return 0; } if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') { *cap = 0; return 0; } /* make sure the lm80 voltage value is updated */ msleep(SSD_LM80_CONV_INTERVAL); /* check if full charged */ wait = 0; for (;;) { ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U1, (uint8_t *)&val); if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS); } goto out; } u1 = SSD_LM80_CONVERT_VOLT(u16_swap(val)); if (SSD_PL_CAP_VOLT(u1) >= SSD_PL_CAP_VOLT_FULL) { break; } wait++; if (wait > SSD_PL_CAP_CHARGE_MAX_WAIT) { ret = -ETIMEDOUT; goto out; } msleep(SSD_PL_CAP_CHARGE_WAIT); } ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U2, (uint8_t *)&val); if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS); } goto out; } u2 = SSD_LM80_CONVERT_VOLT(u16_swap(val)); if (u1 == u2) { ret = -EINVAL; goto out; } /* enter cap learn */ ssd_reg32_write(dev->ctrlp + SSD_PL_CAP_LEARN_REG, 0x1); wait = 0; for (;;) { msleep(SSD_PL_CAP_LEARN_WAIT); t = ssd_reg32_read(dev->ctrlp + SSD_PL_CAP_LEARN_REG); if (!((t >> 1) & 0x1)) { break; } wait++; if (wait > SSD_PL_CAP_LEARN_MAX_WAIT) { ret = -ETIMEDOUT; goto out; } } if ((t >> 4) & 0x1) { ret = -ETIMEDOUT; goto out; } t = (t >> 8); if (0 == t) { ret = -EINVAL; goto out; } *cap = SSD_PL_CAP_LEARN(u1, u2, t); out: return ret; } static int ssd_cap_learn(struct ssd_device *dev, uint32_t *cap) { int ret = 0; if (!dev || !cap) { return -EINVAL; } mutex_lock(&dev->bm_mutex); ssd_stop_workq(dev); ret = ssd_do_cap_learn(dev, cap); if (ret) { ssd_gen_swlog(dev, SSD_LOG_CAP_LEARN_FAULT, 0); goto out; } ssd_gen_swlog(dev, SSD_LOG_CAP_STATUS, *cap); out: ssd_start_workq(dev); mutex_unlock(&dev->bm_mutex); return ret; } static int ssd_check_pl_cap(struct ssd_device *dev) { uint32_t u1; uint16_t val = 0; uint8_t low = 0; int wait = 0; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return 0; } if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') { return 0; } /* cap ready ? */ wait = 0; for (;;) { ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U1, (uint8_t *)&val); if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS); } goto out; } u1 = SSD_LM80_CONVERT_VOLT(u16_swap(val)); if (SSD_PL_CAP_VOLT(u1) >= SSD_PL_CAP_VOLT_READY) { break; } wait++; if (wait > SSD_PL_CAP_CHARGE_MAX_WAIT) { ret = -ETIMEDOUT; ssd_gen_swlog(dev, SSD_LOG_CAP_VOLT_FAULT, SSD_PL_CAP_VOLT(u1)); goto out; } msleep(SSD_PL_CAP_CHARGE_WAIT); } low = ssd_lm80_limit[SSD_LM80_IN_CAP].low; ret = ssd_smbus_write_byte(dev, SSD_SENSOR_LM80_SADDRESS, SSD_LM80_REG_IN_MIN(SSD_LM80_IN_CAP), &low); if (ret) { goto out; } /* enable cap INx */ ret = ssd_lm80_enable_in(dev, SSD_SENSOR_LM80_SADDRESS, SSD_LM80_IN_CAP); if (ret) { if (!test_and_set_bit(SSD_HWMON_SENSOR(SSD_SENSOR_LM80), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_SENSOR_FAULT, SSD_SENSOR_LM80_SADDRESS); } goto out; } out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } static int ssd_check_pl_cap_fast(struct ssd_device *dev) { uint32_t u1; uint16_t val = 0; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return 0; } if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') { return 0; } /* cap ready ? */ ret = ssd_smbus_read_word(dev, SSD_SENSOR_LM80_SADDRESS, SSD_PL_CAP_U1, (uint8_t *)&val); if (ret) { goto out; } u1 = SSD_LM80_CONVERT_VOLT(u16_swap(val)); if (SSD_PL_CAP_VOLT(u1) < SSD_PL_CAP_VOLT_READY) { ret = 1; } out: return ret; } static int ssd_init_pl_cap(struct ssd_device *dev) { int ret = 0; /* set here: user write mode */ dev->user_wmode = wmode; mutex_init(&dev->bm_mutex); if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { uint32_t val; val = ssd_reg32_read(dev->ctrlp + SSD_BM_FAULT_REG); if ((val >> 1) & 0x1) { (void)test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon); } } else { ret = ssd_check_pl_cap(dev); if (ret) { (void)test_and_set_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon); } } return 0; } /* label */ static void __end_str(char *str, int len) { int i; for(i=0; irom_info.label_base; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { size = sizeof(struct ssd_label); /* read label */ ret = ssd_spi_read(dev, &dev->label, off, size); if (ret) { memset(&dev->label, 0, size); goto out; } __end_str(dev->label.date, SSD_LABEL_FIELD_SZ); __end_str(dev->label.sn, SSD_LABEL_FIELD_SZ); __end_str(dev->label.part, SSD_LABEL_FIELD_SZ); __end_str(dev->label.desc, SSD_LABEL_FIELD_SZ); __end_str(dev->label.other, SSD_LABEL_FIELD_SZ); __end_str(dev->label.maf, SSD_LABEL_FIELD_SZ); } else { size = sizeof(struct ssd_labelv3); /* read label */ ret = ssd_spi_read(dev, &dev->labelv3, off, size); if (ret) { memset(&dev->labelv3, 0, size); goto out; } __end_str(dev->labelv3.boardtype, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.barcode, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.item, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.description, SSD_LABEL_DESC_SZ); __end_str(dev->labelv3.manufactured, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.vendorname, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.issuenumber, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.cleicode, SSD_LABEL_FIELD_SZ); __end_str(dev->labelv3.bom, SSD_LABEL_FIELD_SZ); } out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } int ssd_get_label(struct block_device *bdev, struct ssd_label *label) { struct ssd_device *dev; if (!bdev || !label || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { memset(label, 0, sizeof(struct ssd_label)); memcpy(label->date, dev->labelv3.manufactured, SSD_LABEL_FIELD_SZ); memcpy(label->sn, dev->labelv3.barcode, SSD_LABEL_FIELD_SZ); memcpy(label->desc, dev->labelv3.boardtype, SSD_LABEL_FIELD_SZ); memcpy(label->maf, dev->labelv3.vendorname, SSD_LABEL_FIELD_SZ); } else { memcpy(label, &dev->label, sizeof(struct ssd_label)); } return 0; } static int __ssd_get_version(struct ssd_device *dev, struct ssd_version_info *ver) { uint16_t bm_ver = 0; int ret = 0; if (dev->protocol_info.ver > SSD_PROTOCOL_V3 && dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { ret = ssd_bm_get_version(dev, &bm_ver); if(ret){ goto out; } } ver->bridge_ver = dev->hw_info.bridge_ver; ver->ctrl_ver = dev->hw_info.ctrl_ver; ver->bm_ver = bm_ver; ver->pcb_ver = dev->hw_info.pcb_ver; ver->upper_pcb_ver = dev->hw_info.upper_pcb_ver; out: return ret; } int ssd_get_version(struct block_device *bdev, struct ssd_version_info *ver) { struct ssd_device *dev; int ret; if (!bdev || !ver || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; mutex_lock(&dev->fw_mutex); ret = __ssd_get_version(dev, ver); mutex_unlock(&dev->fw_mutex); return ret; } static int __ssd_get_temperature(struct ssd_device *dev, int *temp) { uint64_t val; uint32_t off; int max = -300; int cur; int i; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { *temp = 0; return 0; } if (finject) { if (dev->db_info.type == SSD_DEBUG_LOG && (dev->db_info.data.log.event == SSD_LOG_OVER_TEMP || dev->db_info.data.log.event == SSD_LOG_NORMAL_TEMP || dev->db_info.data.log.event == SSD_LOG_WARN_TEMP)) { *temp = (int)dev->db_info.data.log.extra; return 0; } } for (i=0; ihw_info.nr_ctrl; i++) { off = SSD_CTRL_TEMP_REG0 + i * sizeof(uint64_t); val = ssd_reg_read(dev->ctrlp + off); if (val == 0xffffffffffffffffull) { continue; } cur = (int)CUR_TEMP(val); if (cur >= max) { max = cur; } } *temp = max; return 0; } int ssd_get_temperature(struct block_device *bdev, int *temp) { struct ssd_device *dev; int ret; if (!bdev || !temp || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; mutex_lock(&dev->fw_mutex); ret = __ssd_get_temperature(dev, temp); mutex_unlock(&dev->fw_mutex); return ret; } int ssd_set_otprotect(struct block_device *bdev, int otprotect) { struct ssd_device *dev; if (!bdev || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; ssd_set_ot_protect(dev, !!otprotect); return 0; } int ssd_bm_status(struct block_device *bdev, int *status) { struct ssd_device *dev; int ret = 0; if (!bdev || !status || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; mutex_lock(&dev->fw_mutex); if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { *status = SSD_BMSTATUS_WARNING; } else { *status = SSD_BMSTATUS_OK; } } else if(dev->protocol_info.ver > SSD_PROTOCOL_V3) { ret = __ssd_bm_status(dev, status); } else { *status = SSD_BMSTATUS_OK; } mutex_unlock(&dev->fw_mutex); return ret; } int ssd_get_pciaddr(struct block_device *bdev, struct pci_addr *paddr) { struct ssd_device *dev; if (!bdev || !paddr || !bdev->bd_disk) { return -EINVAL; } dev = bdev->bd_disk->private_data; paddr->domain = pci_domain_nr(dev->pdev->bus); paddr->bus = dev->pdev->bus->number; paddr->slot = PCI_SLOT(dev->pdev->devfn); paddr->func= PCI_FUNC(dev->pdev->devfn); return 0; } /* acc */ static int ssd_bb_acc(struct ssd_device *dev, struct ssd_acc_info *acc) { uint32_t val; int ctrl, chip; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) { return -EOPNOTSUPP; } acc->threshold_l1 = ssd_reg32_read(dev->ctrlp + SSD_BB_THRESHOLD_L1_REG); if (0xffffffffull == acc->threshold_l1) { return -EIO; } acc->threshold_l2 = ssd_reg32_read(dev->ctrlp + SSD_BB_THRESHOLD_L2_REG); if (0xffffffffull == acc->threshold_l2) { return -EIO; } acc->val = 0; for (ctrl=0; ctrlhw_info.nr_ctrl; ctrl++) { for (chip=0; chiphw_info.nr_chip; chip++) { val = ssd_reg32_read(dev->ctrlp + SSD_BB_ACC_REG0 + (SSD_CTRL_REG_ZONE_SZ * ctrl) + (SSD_BB_ACC_REG_SZ * chip)); if (0xffffffffull == acc->val) { return -EIO; } if (val > acc->val) { acc->val = val; } } } return 0; } static int ssd_ec_acc(struct ssd_device *dev, struct ssd_acc_info *acc) { uint32_t val; int ctrl, chip; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) { return -EOPNOTSUPP; } acc->threshold_l1 = ssd_reg32_read(dev->ctrlp + SSD_EC_THRESHOLD_L1_REG); if (0xffffffffull == acc->threshold_l1) { return -EIO; } acc->threshold_l2 = ssd_reg32_read(dev->ctrlp + SSD_EC_THRESHOLD_L2_REG); if (0xffffffffull == acc->threshold_l2) { return -EIO; } acc->val = 0; for (ctrl=0; ctrlhw_info.nr_ctrl; ctrl++) { for (chip=0; chiphw_info.nr_chip; chip++) { val = ssd_reg32_read(dev->ctrlp + SSD_EC_ACC_REG0 + (SSD_CTRL_REG_ZONE_SZ * ctrl) + (SSD_EC_ACC_REG_SZ * chip)); if (0xffffffffull == acc->val) { return -EIO; } if (val > acc->val) { acc->val = val; } } } return 0; } /* ram r&w */ static int ssd_ram_read_4k(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx) { struct ssd_ram_op_msg *msg; dma_addr_t buf_dma; size_t len = length; loff_t ofs_w = ofs; int ret = 0; if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size || !length || length > dev->hw_info.ram_max_len || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) { return -EINVAL; } len /= dev->hw_info.ram_align; do_div(ofs_w, dev->hw_info.ram_align); buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map read DMA buffer\n", dev->name); goto out_dma_mapping; } msg = (struct ssd_ram_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_RAM_READ; msg->ctrl_idx = ctrl_idx; msg->start = (uint32_t)ofs_w; msg->length = len; msg->buf = buf_dma; ret = ssd_do_request(dev, READ, msg, NULL); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE); out_dma_mapping: return ret; } static int ssd_ram_write_4k(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx) { struct ssd_ram_op_msg *msg; dma_addr_t buf_dma; size_t len = length; loff_t ofs_w = ofs; int ret = 0; if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size || !length || length > dev->hw_info.ram_max_len || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) { return -EINVAL; } len /= dev->hw_info.ram_align; do_div(ofs_w, dev->hw_info.ram_align); buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_TODEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map write DMA buffer\n", dev->name); goto out_dma_mapping; } msg = (struct ssd_ram_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_RAM_WRITE; msg->ctrl_idx = ctrl_idx; msg->start = (uint32_t)ofs_w; msg->length = len; msg->buf = buf_dma; ret = ssd_do_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_TODEVICE); out_dma_mapping: return ret; } static int ssd_ram_read(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx) { int left = length; size_t len; loff_t off = ofs; int ret = 0; if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size || !length || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) { return -EINVAL; } while (left > 0) { len = dev->hw_info.ram_max_len; if (left < (int)dev->hw_info.ram_max_len) { len = left; } ret = ssd_ram_read_4k(dev, buf, len, off, ctrl_idx); if (ret) { break; } left -= len; off += len; buf += len; } return ret; } static int ssd_ram_write(struct ssd_device *dev, void *buf, size_t length, loff_t ofs, int ctrl_idx) { int left = length; size_t len; loff_t off = ofs; int ret = 0; if (ctrl_idx >= dev->hw_info.nr_ctrl || (uint64_t)(ofs + length) > dev->hw_info.ram_size || !length || (length & (dev->hw_info.ram_align - 1)) != 0 || ((uint64_t)ofs & (dev->hw_info.ram_align - 1)) != 0) { return -EINVAL; } while (left > 0) { len = dev->hw_info.ram_max_len; if (left < (int)dev->hw_info.ram_max_len) { len = left; } ret = ssd_ram_write_4k(dev, buf, len, off, ctrl_idx); if (ret) { break; } left -= len; off += len; buf += len; } return ret; } /* flash op */ static int ssd_check_flash(struct ssd_device *dev, int flash, int page, int ctrl_idx) { int cur_ch = flash % dev->hw_info.max_ch; int cur_chip = flash /dev->hw_info.max_ch; if (ctrl_idx >= dev->hw_info.nr_ctrl) { return -EINVAL; } if (cur_ch >= dev->hw_info.nr_ch || cur_chip >= dev->hw_info.nr_chip) { return -EINVAL; } if (page >= (int)(dev->hw_info.block_count * dev->hw_info.page_count)) { return -EINVAL; } return 0; } static int ssd_nand_read_id(struct ssd_device *dev, void *id, int flash, int chip, int ctrl_idx) { struct ssd_nand_op_msg *msg; dma_addr_t buf_dma; int ret = 0; if (unlikely(!id)) return -EINVAL; buf_dma = pci_map_single(dev->pdev, id, SSD_NAND_ID_BUFF_SZ, PCI_DMA_FROMDEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map read DMA buffer\n", dev->name); goto out_dma_mapping; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { flash = ((uint32_t)flash << 1) | (uint32_t)chip; chip = 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_NAND_READ_ID; msg->chip_no = flash; msg->chip_ce = chip; msg->ctrl_idx = ctrl_idx; msg->buf = buf_dma; ret = ssd_do_request(dev, READ, msg, NULL); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, SSD_NAND_ID_BUFF_SZ, PCI_DMA_FROMDEVICE); out_dma_mapping: return ret; } #if 0 static int ssd_nand_read(struct ssd_device *dev, void *buf, int flash, int chip, int page, int page_count, int ctrl_idx) { struct ssd_nand_op_msg *msg; dma_addr_t buf_dma; int length; int ret = 0; if (!buf) { return -EINVAL; } if ((page + page_count) > dev->hw_info.block_count*dev->hw_info.page_count) { return -EINVAL; } ret = ssd_check_flash(dev, flash, page, ctrl_idx); if (ret) { return ret; } length = page_count * dev->hw_info.page_size; buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map read DMA buffer\n", dev->name); goto out_dma_mapping; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { flash = (flash << 1) | chip; chip = 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_NAND_READ; msg->ctrl_idx = ctrl_idx; msg->chip_no = flash; msg->chip_ce = chip; msg->page_no = page; msg->page_count = page_count; msg->buf = buf_dma; ret = ssd_do_request(dev, READ, msg, NULL); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE); out_dma_mapping: return ret; } #endif static int ssd_nand_read_w_oob(struct ssd_device *dev, void *buf, int flash, int chip, int page, int count, int ctrl_idx) { struct ssd_nand_op_msg *msg; dma_addr_t buf_dma; int length; int ret = 0; if (!buf) { return -EINVAL; } if ((page + count) > (int)(dev->hw_info.block_count * dev->hw_info.page_count)) { return -EINVAL; } ret = ssd_check_flash(dev, flash, page, ctrl_idx); if (ret) { return ret; } length = count * (dev->hw_info.page_size + dev->hw_info.oob_size); buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_FROMDEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map read DMA buffer\n", dev->name); goto out_dma_mapping; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { flash = ((uint32_t)flash << 1) | (uint32_t)chip; chip = 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_NAND_READ_WOOB; msg->ctrl_idx = ctrl_idx; msg->chip_no = flash; msg->chip_ce = chip; msg->page_no = page; msg->page_count = count; msg->buf = buf_dma; ret = ssd_do_request(dev, READ, msg, NULL); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_FROMDEVICE); out_dma_mapping: return ret; } /* write 1 page */ static int ssd_nand_write(struct ssd_device *dev, void *buf, int flash, int chip, int page, int count, int ctrl_idx) { struct ssd_nand_op_msg *msg; dma_addr_t buf_dma; int length; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { return -EINVAL; } if (!buf) { return -EINVAL; } if (count != 1) { return -EINVAL; } ret = ssd_check_flash(dev, flash, page, ctrl_idx); if (ret) { return ret; } length = count * (dev->hw_info.page_size + dev->hw_info.oob_size); /* write data to ram */ /*ret = ssd_ram_write(dev, buf, length, dev->hw_info.nand_wbuff_base, ctrl_idx); if (ret) { return ret; }*/ buf_dma = pci_map_single(dev->pdev, buf, length, PCI_DMA_TODEVICE); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) ret = dma_mapping_error(buf_dma); #else ret = dma_mapping_error(&(dev->pdev->dev), buf_dma); #endif if (ret) { hio_warn("%s: unable to map write DMA buffer\n", dev->name); goto out_dma_mapping; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { flash = ((uint32_t)flash << 1) | (uint32_t)chip; chip = 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_NAND_WRITE; msg->ctrl_idx = ctrl_idx; msg->chip_no = flash; msg->chip_ce = chip; msg->page_no = page; msg->page_count = count; msg->buf = buf_dma; ret = ssd_do_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); pci_unmap_single(dev->pdev, buf_dma, length, PCI_DMA_TODEVICE); out_dma_mapping: return ret; } static int ssd_nand_erase(struct ssd_device *dev, int flash, int chip, int page, int ctrl_idx) { struct ssd_nand_op_msg *msg; int ret = 0; ret = ssd_check_flash(dev, flash, page, ctrl_idx); if (ret) { return ret; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { flash = ((uint32_t)flash << 1) | (uint32_t)chip; chip = 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_NAND_ERASE; msg->ctrl_idx = ctrl_idx; msg->chip_no = flash; msg->chip_ce = chip; msg->page_no = page; ret = ssd_do_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); return ret; } static int ssd_update_bbt(struct ssd_device *dev, int flash, int ctrl_idx) { struct ssd_nand_op_msg *msg; struct ssd_flush_msg *fmsg; int ret = 0; ret = ssd_check_flash(dev, flash, 0, ctrl_idx); if (ret) { return ret; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { fmsg = (struct ssd_flush_msg *)msg; fmsg->fun = SSD_FUNC_FLUSH; fmsg->flag = 0x1; fmsg->flash = flash; fmsg->ctrl_idx = ctrl_idx; } else { msg->fun = SSD_FUNC_FLUSH; msg->flag = 0x1; msg->chip_no = flash; msg->ctrl_idx = ctrl_idx; } ret = ssd_do_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); return ret; } /* flash controller init state */ static int __ssd_check_init_state(struct ssd_device *dev) { uint32_t *init_state = NULL; int reg_base, reg_sz; int max_wait = SSD_INIT_MAX_WAIT; int init_wait = 0; int i, j, k; int ch_start = 0; /* for (i=0; ihw_info.nr_ctrl; i++) { ssd_reg32_write(dev->ctrlp + SSD_CTRL_TEST_REG0 + i * 8, test_data); read_data = ssd_reg32_read(dev->ctrlp + SSD_CTRL_TEST_REG0 + i * 8); if (read_data == ~test_data) { //dev->hw_info.nr_ctrl++; dev->hw_info.nr_ctrl_map |= 1<ctrlp + SSD_READY_REG); j=0; for (i=0; ihw_info.nr_ctrl; i++) { if (((read_data>>i) & 0x1) == 0) { j++; } } if (dev->hw_info.nr_ctrl != j) { printk(KERN_WARNING "%s: nr_ctrl mismatch: %d %d\n", dev->name, dev->hw_info.nr_ctrl, j); return -1; } */ /* init_state = ssd_reg_read(dev->ctrlp + SSD_FLASH_INFO_REG0); for (j=1; jhw_info.nr_ctrl;j++) { if (init_state != ssd_reg_read(dev->ctrlp + SSD_FLASH_INFO_REG0 + j*8)) { printk(KERN_WARNING "SSD_FLASH_INFO_REG[%d], not match\n", j); return -1; } } */ /* init_state = ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0); for (j=1; jhw_info.nr_ctrl; j++) { if (init_state != ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0 + j*16)) { printk(KERN_WARNING "SSD_CHIP_INFO_REG Lo [%d], not match\n", j); return -1; } } init_state = ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0 + 8); for (j=1; jhw_info.nr_ctrl; j++) { if (init_state != ssd_reg_read(dev->ctrlp + SSD_CHIP_INFO_REG0 + 8 + j*16)) { printk(KERN_WARNING "SSD_CHIP_INFO_REG Hi [%d], not match\n", j); return -1; } } */ if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { max_wait = SSD_INIT_MAX_WAIT_V3_2; } reg_base = dev->protocol_info.init_state_reg; reg_sz = dev->protocol_info.init_state_reg_sz; init_state = (uint32_t *)kmalloc(reg_sz, GFP_KERNEL); if (!init_state) { return -ENOMEM; } for (i=0; ihw_info.nr_ctrl; i++) { check_init: for (j=0, k=0; jctrlp + reg_base + j); } if (dev->protocol_info.ver > SSD_PROTOCOL_V3) { /* just check the last bit, no need to check all channel */ ch_start = dev->hw_info.max_ch - 1; } else { ch_start = 0; } for (j=0; jhw_info.nr_chip; j++) { for (k=ch_start; khw_info.max_ch; k++) { if (test_bit((j*dev->hw_info.max_ch + k), (void *)init_state)) { continue; } init_wait++; if (init_wait <= max_wait) { msleep(SSD_INIT_WAIT); goto check_init; } else { if (k < dev->hw_info.nr_ch) { hio_warn("%s: controller %d chip %d ch %d init failed\n", dev->name, i, j, k); } else { hio_warn("%s: controller %d chip %d init failed\n", dev->name, i, j); } kfree(init_state); return -1; } } } reg_base += reg_sz; } //printk(KERN_WARNING "%s: init wait %d\n", dev->name, init_wait); kfree(init_state); return 0; } static int ssd_check_init_state(struct ssd_device *dev) { if (mode != SSD_DRV_MODE_STANDARD) { return 0; } return __ssd_check_init_state(dev); } static void ssd_reset_resp_ptr(struct ssd_device *dev); /* reset flash controller etc */ static int __ssd_reset(struct ssd_device *dev, int type) { if (type < SSD_RST_NOINIT || type > SSD_RST_FULL) { return -EINVAL; } mutex_lock(&dev->fw_mutex); if (type == SSD_RST_NOINIT) { //no init ssd_reg32_write(dev->ctrlp + SSD_RESET_REG, SSD_RESET_NOINIT); } else if (type == SSD_RST_NORMAL) { //reset & init ssd_reg32_write(dev->ctrlp + SSD_RESET_REG, SSD_RESET); } else { // full reset if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { mutex_unlock(&dev->fw_mutex); return -EINVAL; } ssd_reg32_write(dev->ctrlp + SSD_FULL_RESET_REG, SSD_RESET_FULL); /* ?? */ ssd_reset_resp_ptr(dev); } #ifdef SSD_OT_PROTECT dev->ot_delay = 0; #endif msleep(1000); /* xx */ ssd_set_flush_timeout(dev, dev->wmode); mutex_unlock(&dev->fw_mutex); ssd_gen_swlog(dev, SSD_LOG_RESET, (uint32_t)type); return __ssd_check_init_state(dev); } static int ssd_save_md(struct ssd_device *dev) { struct ssd_nand_op_msg *msg; int ret = 0; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } if (!dev->save_md) { return 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_FLUSH; msg->flag = 0x2; msg->ctrl_idx = 0; msg->chip_no = 0; ret = ssd_do_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); return ret; } static int ssd_barrier_save_md(struct ssd_device *dev) { struct ssd_nand_op_msg *msg; int ret = 0; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return 0; } if (!dev->save_md) { return 0; } msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); msg->fun = SSD_FUNC_FLUSH; msg->flag = 0x2; msg->ctrl_idx = 0; msg->chip_no = 0; ret = ssd_do_barrier_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); return ret; } static int ssd_flush(struct ssd_device *dev) { struct ssd_nand_op_msg *msg; struct ssd_flush_msg *fmsg; int ret = 0; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { fmsg = (struct ssd_flush_msg *)msg; fmsg->fun = SSD_FUNC_FLUSH; fmsg->flag = 0; fmsg->ctrl_idx = 0; fmsg->flash = 0; } else { msg->fun = SSD_FUNC_FLUSH; msg->flag = 0; msg->ctrl_idx = 0; msg->chip_no = 0; } ret = ssd_do_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); return ret; } static int ssd_barrier_flush(struct ssd_device *dev) { struct ssd_nand_op_msg *msg; struct ssd_flush_msg *fmsg; int ret = 0; if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; msg = (struct ssd_nand_op_msg *)ssd_get_dmsg(dev); if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { fmsg = (struct ssd_flush_msg *)msg; fmsg->fun = SSD_FUNC_FLUSH; fmsg->flag = 0; fmsg->ctrl_idx = 0; fmsg->flash = 0; } else { msg->fun = SSD_FUNC_FLUSH; msg->flag = 0; msg->ctrl_idx = 0; msg->chip_no = 0; } ret = ssd_do_barrier_request(dev, WRITE, msg, NULL); ssd_put_dmsg(msg); return ret; } #define SSD_WMODE_BUFFER_TIMEOUT 0x00c82710 #define SSD_WMODE_BUFFER_EX_TIMEOUT 0x000500c8 #define SSD_WMODE_FUA_TIMEOUT 0x000503E8 static void ssd_set_flush_timeout(struct ssd_device *dev, int m) { uint32_t to; uint32_t val = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) { return; } switch(m) { case SSD_WMODE_BUFFER: to = SSD_WMODE_BUFFER_TIMEOUT; break; case SSD_WMODE_BUFFER_EX: if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2_1) { to = SSD_WMODE_BUFFER_EX_TIMEOUT; } else { to = SSD_WMODE_BUFFER_TIMEOUT; } break; case SSD_WMODE_FUA: to = SSD_WMODE_FUA_TIMEOUT; break; default: return; } val = (((uint32_t)((uint32_t)m & 0x3) << 28) | to); ssd_reg32_write(dev->ctrlp + SSD_FLUSH_TIMEOUT_REG, val); } static int ssd_do_switch_wmode(struct ssd_device *dev, int m) { int ret = 0; ret = ssd_barrier_start(dev); if (ret) { goto out; } ret = ssd_barrier_flush(dev); if (ret) { goto out_barrier_end; } /* set contoller flush timeout */ ssd_set_flush_timeout(dev, m); dev->wmode = m; mb(); out_barrier_end: ssd_barrier_end(dev); out: return ret; } static int ssd_switch_wmode(struct ssd_device *dev, int m) { int default_wmode; int next_wmode; int ret = 0; if (!test_bit(SSD_ONLINE, &dev->state)) { return -ENODEV; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { default_wmode = SSD_WMODE_BUFFER; } else { default_wmode = SSD_WMODE_BUFFER_EX; } if (SSD_WMODE_AUTO == m) { /* battery fault ? */ if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { next_wmode = SSD_WMODE_FUA; } else { next_wmode = default_wmode; } } else if (SSD_WMODE_DEFAULT == m) { next_wmode = default_wmode; } else { next_wmode = m; } if (next_wmode != dev->wmode) { hio_warn("%s: switch write mode (%d -> %d)\n", dev->name, dev->wmode, next_wmode); ret = ssd_do_switch_wmode(dev, next_wmode); if (ret) { hio_err("%s: can not switch write mode (%d -> %d)\n", dev->name, dev->wmode, next_wmode); } } return ret; } static int ssd_init_wmode(struct ssd_device *dev) { int default_wmode; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { default_wmode = SSD_WMODE_BUFFER; } else { default_wmode = SSD_WMODE_BUFFER_EX; } /* dummy mode */ if (SSD_WMODE_AUTO == dev->user_wmode) { /* battery fault ? */ if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { dev->wmode = SSD_WMODE_FUA; } else { dev->wmode = default_wmode; } } else if (SSD_WMODE_DEFAULT == dev->user_wmode) { dev->wmode = default_wmode; } else { dev->wmode = dev->user_wmode; } ssd_set_flush_timeout(dev, dev->wmode); return ret; } static int __ssd_set_wmode(struct ssd_device *dev, int m) { int ret = 0; /* not support old fw*/ if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_1) { ret = -EOPNOTSUPP; goto out; } if (m < SSD_WMODE_BUFFER || m > SSD_WMODE_DEFAULT) { ret = -EINVAL; goto out; } ssd_gen_swlog(dev, SSD_LOG_SET_WMODE, m); dev->user_wmode = m; ret = ssd_switch_wmode(dev, dev->user_wmode); if (ret) { goto out; } out: return ret; } int ssd_set_wmode(struct block_device *bdev, int m) { struct ssd_device *dev; if (!bdev || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; return __ssd_set_wmode(dev, m); } static int ssd_do_reset(struct ssd_device *dev) { int ret = 0; if (test_and_set_bit(SSD_RESETING, &dev->state)) { return 0; } ssd_stop_workq(dev); ret = ssd_barrier_start(dev); if (ret) { goto out; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { /* old reset */ ret = __ssd_reset(dev, SSD_RST_NORMAL); } else { /* full reset */ //ret = __ssd_reset(dev, SSD_RST_FULL); ret = __ssd_reset(dev, SSD_RST_NORMAL); } if (ret) { goto out_barrier_end; } out_barrier_end: ssd_barrier_end(dev); out: ssd_start_workq(dev); test_and_clear_bit(SSD_RESETING, &dev->state); return ret; } static int ssd_full_reset(struct ssd_device *dev) { int ret = 0; if (test_and_set_bit(SSD_RESETING, &dev->state)) { return 0; } ssd_stop_workq(dev); ret = ssd_barrier_start(dev); if (ret) { goto out; } ret = ssd_barrier_flush(dev); if (ret) { goto out_barrier_end; } ret = ssd_barrier_save_md(dev); if (ret) { goto out_barrier_end; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { /* old reset */ ret = __ssd_reset(dev, SSD_RST_NORMAL); } else { /* full reset */ //ret = __ssd_reset(dev, SSD_RST_FULL); ret = __ssd_reset(dev, SSD_RST_NORMAL); } if (ret) { goto out_barrier_end; } out_barrier_end: ssd_barrier_end(dev); out: ssd_start_workq(dev); test_and_clear_bit(SSD_RESETING, &dev->state); return ret; } int ssd_reset(struct block_device *bdev) { struct ssd_device *dev; if (!bdev || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; return ssd_full_reset(dev); } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) static int ssd_issue_flush_fn(struct request_queue *q, struct gendisk *disk, sector_t *error_sector) { struct ssd_device *dev = q->queuedata; return ssd_flush(dev); } #endif void ssd_submit_pbio(struct request_queue *q, struct bio *bio) { struct ssd_device *dev = q->queuedata; #ifdef SSD_QUEUE_PBIO int ret = -EBUSY; #endif if (!test_bit(SSD_ONLINE, &dev->state)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -ENODEV); #else bio_endio(bio, bio->bi_size, -ENODEV); #endif goto out; } #ifdef SSD_DEBUG_ERR if (atomic_read(&dev->tocnt)) { hio_warn("%s: IO rejected because of IO timeout!\n", dev->name); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EIO); #else bio_endio(bio, bio->bi_size, -EIO); #endif goto out; } #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) if (unlikely(bio_barrier(bio))) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)) if (unlikely(bio_rw_flagged(bio, BIO_RW_BARRIER))) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)) if (unlikely(bio->bi_rw & REQ_HARDBARRIER)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #else //xx if (unlikely(bio->bi_rw & REQ_FUA)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #endif if (unlikely(dev->readonly && bio_data_dir(bio) == WRITE)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EROFS); #else bio_endio(bio, bio->bi_size, -EROFS); #endif goto out; } #ifdef SSD_QUEUE_PBIO if (0 == atomic_read(&dev->in_sendq)) { ret = __ssd_submit_pbio(dev, bio, 0); } if (ret) { (void)test_and_set_bit(BIO_SSD_PBIO, &bio->bi_flags); ssd_queue_bio(dev, bio); } #else __ssd_submit_pbio(dev, bio, 1); #endif out: return; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)) static int ssd_make_request(struct request_queue *q, struct bio *bio) #else static void ssd_make_request(struct request_queue *q, struct bio *bio) #endif { struct ssd_device *dev = q->queuedata; int ret = -EBUSY; if (!test_bit(SSD_ONLINE, &dev->state)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -ENODEV); #else bio_endio(bio, bio->bi_size, -ENODEV); #endif goto out; } #ifdef SSD_DEBUG_ERR if (atomic_read(&dev->tocnt)) { hio_warn("%s: IO rejected because of IO timeout!\n", dev->name); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EIO); #else bio_endio(bio, bio->bi_size, -EIO); #endif goto out; } #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) if (unlikely(bio_barrier(bio))) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)) if (unlikely(bio_rw_flagged(bio, BIO_RW_BARRIER))) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)) if (unlikely(bio->bi_rw & REQ_HARDBARRIER)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } #else //xx if (unlikely(bio->bi_rw & REQ_FUA)) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)) bio_endio(bio, -EOPNOTSUPP); #else bio_endio(bio, bio->bi_size, -EOPNOTSUPP); #endif goto out; } /* writeback_cache_control.txt: REQ_FLUSH requests without data can be completed successfully without doing any work */ if (unlikely((bio->bi_rw & REQ_FLUSH) && !bio_sectors(bio))) { bio_endio(bio, 0); goto out; } #endif if (0 == atomic_read(&dev->in_sendq)) { ret = ssd_submit_bio(dev, bio, 0); } if (ret) { ssd_queue_bio(dev, bio); } out: #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)) return 0; #else return; #endif } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)) static int ssd_block_getgeo(struct block_device *bdev, struct hd_geometry *geo) { struct ssd_device *dev; if (!bdev) { return -EINVAL; } dev = bdev->bd_disk->private_data; if (!dev) { return -EINVAL; } geo->heads = 4; geo->sectors = 16; geo->cylinders = (dev->hw_info.size & ~0x3f) >> 6; return 0; } #endif static void ssd_cleanup_blkdev(struct ssd_device *dev); static int ssd_init_blkdev(struct ssd_device *dev); static int ssd_ioctl_common(struct ssd_device *dev, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; void __user *buf = NULL; void *kbuf = NULL; int ret = 0; switch (cmd) { case SSD_CMD_GET_PROTOCOL_INFO: if (copy_to_user(argp, &dev->protocol_info, sizeof(struct ssd_protocol_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; case SSD_CMD_GET_HW_INFO: if (copy_to_user(argp, &dev->hw_info, sizeof(struct ssd_hw_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; case SSD_CMD_GET_ROM_INFO: if (copy_to_user(argp, &dev->rom_info, sizeof(struct ssd_rom_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; case SSD_CMD_GET_SMART: { struct ssd_smart smart; int i; memcpy(&smart, &dev->smart, sizeof(struct ssd_smart)); mutex_lock(&dev->gd_mutex); ssd_update_smart(dev, &smart); mutex_unlock(&dev->gd_mutex); /* combine the volatile log info */ if (dev->log_info.nr_log) { for (i=0; ilog_info.stat[i]; } } if (copy_to_user(argp, &smart, sizeof(struct ssd_smart))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_IDX: if (copy_to_user(argp, &dev->idx, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; case SSD_CMD_GET_AMOUNT: { int nr_ssd = atomic_read(&ssd_nr); if (copy_to_user(argp, &nr_ssd, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_TO_INFO: { int tocnt = atomic_read(&dev->tocnt); if (copy_to_user(argp, &tocnt, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_DRV_VER: { char ver[] = DRIVER_VERSION; int len = sizeof(ver); if (len > (DRIVER_VERSION_LEN - 1)) { len = (DRIVER_VERSION_LEN - 1); } if (copy_to_user(argp, ver, len)) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_BBACC_INFO: { struct ssd_acc_info acc; mutex_lock(&dev->fw_mutex); ret = ssd_bb_acc(dev, &acc); mutex_unlock(&dev->fw_mutex); if (ret) { break; } if (copy_to_user(argp, &acc, sizeof(struct ssd_acc_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_ECACC_INFO: { struct ssd_acc_info acc; mutex_lock(&dev->fw_mutex); ret = ssd_ec_acc(dev, &acc); mutex_unlock(&dev->fw_mutex); if (ret) { break; } if (copy_to_user(argp, &acc, sizeof(struct ssd_acc_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_HW_INFO_EXT: if (copy_to_user(argp, &dev->hw_info_ext, sizeof(struct ssd_hw_info_extend))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; case SSD_CMD_REG_READ: { struct ssd_reg_op_info reg_info; if (copy_from_user(®_info, argp, sizeof(struct ssd_reg_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } if (reg_info.offset > dev->mmio_len-sizeof(uint32_t)) { ret = -EINVAL; break; } reg_info.value = ssd_reg32_read(dev->ctrlp + reg_info.offset); if (copy_to_user(argp, ®_info, sizeof(struct ssd_reg_op_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_REG_WRITE: { struct ssd_reg_op_info reg_info; if (copy_from_user(®_info, argp, sizeof(struct ssd_reg_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } if (reg_info.offset > dev->mmio_len-sizeof(uint32_t)) { ret = -EINVAL; break; } ssd_reg32_write(dev->ctrlp + reg_info.offset, reg_info.value); break; } case SSD_CMD_SPI_READ: { struct ssd_spi_op_info spi_info; uint32_t off, size; if (copy_from_user(&spi_info, argp, sizeof(struct ssd_spi_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } off = spi_info.off; size = spi_info.len; buf = spi_info.buf; if (size > dev->rom_info.size || 0 == size || (off + size) > dev->rom_info.size) { ret = -EINVAL; break; } kbuf = kmalloc(size, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } ret = ssd_spi_page_read(dev, kbuf, off, size); if (ret) { kfree(kbuf); break; } if (copy_to_user(buf, kbuf, size)) { hio_warn("%s: copy_to_user: failed\n", dev->name); kfree(kbuf); ret = -EFAULT; break; } kfree(kbuf); break; } case SSD_CMD_SPI_WRITE: { struct ssd_spi_op_info spi_info; uint32_t off, size; if (copy_from_user(&spi_info, argp, sizeof(struct ssd_spi_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } off = spi_info.off; size = spi_info.len; buf = spi_info.buf; if (size > dev->rom_info.size || 0 == size || (off + size) > dev->rom_info.size) { ret = -EINVAL; break; } kbuf = kmalloc(size, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } if (copy_from_user(kbuf, buf, size)) { hio_warn("%s: copy_from_user: failed\n", dev->name); kfree(kbuf); ret = -EFAULT; break; } ret = ssd_spi_page_write(dev, kbuf, off, size); if (ret) { kfree(kbuf); break; } kfree(kbuf); break; } case SSD_CMD_SPI_ERASE: { struct ssd_spi_op_info spi_info; uint32_t off; if (copy_from_user(&spi_info, argp, sizeof(struct ssd_spi_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } off = spi_info.off; if ((off + dev->rom_info.block_size) > dev->rom_info.size) { ret = -EINVAL; break; } ret = ssd_spi_block_erase(dev, off); if (ret) { break; } break; } case SSD_CMD_I2C_READ: { struct ssd_i2c_op_info i2c_info; uint8_t saddr; uint8_t rsize; if (copy_from_user(&i2c_info, argp, sizeof(struct ssd_i2c_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = i2c_info.saddr; rsize = i2c_info.rsize; buf = i2c_info.rbuf; if (rsize <= 0 || rsize > SSD_I2C_MAX_DATA) { ret = -EINVAL; break; } kbuf = kmalloc(rsize, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } ret = ssd_i2c_read(dev, saddr, rsize, kbuf); if (ret) { kfree(kbuf); break; } if (copy_to_user(buf, kbuf, rsize)) { hio_warn("%s: copy_to_user: failed\n", dev->name); kfree(kbuf); ret = -EFAULT; break; } kfree(kbuf); break; } case SSD_CMD_I2C_WRITE: { struct ssd_i2c_op_info i2c_info; uint8_t saddr; uint8_t wsize; if (copy_from_user(&i2c_info, argp, sizeof(struct ssd_i2c_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = i2c_info.saddr; wsize = i2c_info.wsize; buf = i2c_info.wbuf; if (wsize <= 0 || wsize > SSD_I2C_MAX_DATA) { ret = -EINVAL; break; } kbuf = kmalloc(wsize, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } if (copy_from_user(kbuf, buf, wsize)) { hio_warn("%s: copy_from_user: failed\n", dev->name); kfree(kbuf); ret = -EFAULT; break; } ret = ssd_i2c_write(dev, saddr, wsize, kbuf); if (ret) { kfree(kbuf); break; } kfree(kbuf); break; } case SSD_CMD_I2C_WRITE_READ: { struct ssd_i2c_op_info i2c_info; uint8_t saddr; uint8_t wsize; uint8_t rsize; uint8_t size; if (copy_from_user(&i2c_info, argp, sizeof(struct ssd_i2c_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = i2c_info.saddr; wsize = i2c_info.wsize; rsize = i2c_info.rsize; buf = i2c_info.wbuf; if (wsize <= 0 || wsize > SSD_I2C_MAX_DATA) { ret = -EINVAL; break; } if (rsize <= 0 || rsize > SSD_I2C_MAX_DATA) { ret = -EINVAL; break; } size = wsize + rsize; kbuf = kmalloc(size, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } if (copy_from_user((kbuf + rsize), buf, wsize)) { hio_warn("%s: copy_from_user: failed\n", dev->name); kfree(kbuf); ret = -EFAULT; break; } buf = i2c_info.rbuf; ret = ssd_i2c_write_read(dev, saddr, wsize, (kbuf + rsize), rsize, kbuf); if (ret) { kfree(kbuf); break; } if (copy_to_user(buf, kbuf, rsize)) { hio_warn("%s: copy_to_user: failed\n", dev->name); kfree(kbuf); ret = -EFAULT; break; } kfree(kbuf); break; } case SSD_CMD_SMBUS_SEND_BYTE: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; buf = smbus_info.buf; size = 1; if (copy_from_user(smb_data, buf, size)) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ret = ssd_smbus_send_byte(dev, saddr, smb_data); if (ret) { break; } break; } case SSD_CMD_SMBUS_RECEIVE_BYTE: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; buf = smbus_info.buf; size = 1; ret = ssd_smbus_receive_byte(dev, saddr, smb_data); if (ret) { break; } if (copy_to_user(buf, smb_data, size)) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_SMBUS_WRITE_BYTE: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t command; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; command = smbus_info.cmd; buf = smbus_info.buf; size = 1; if (copy_from_user(smb_data, buf, size)) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ret = ssd_smbus_write_byte(dev, saddr, command, smb_data); if (ret) { break; } break; } case SSD_CMD_SMBUS_READ_BYTE: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t command; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; command = smbus_info.cmd; buf = smbus_info.buf; size = 1; ret = ssd_smbus_read_byte(dev, saddr, command, smb_data); if (ret) { break; } if (copy_to_user(buf, smb_data, size)) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_SMBUS_WRITE_WORD: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t command; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; command = smbus_info.cmd; buf = smbus_info.buf; size = 2; if (copy_from_user(smb_data, buf, size)) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ret = ssd_smbus_write_word(dev, saddr, command, smb_data); if (ret) { break; } break; } case SSD_CMD_SMBUS_READ_WORD: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t command; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; command = smbus_info.cmd; buf = smbus_info.buf; size = 2; ret = ssd_smbus_read_word(dev, saddr, command, smb_data); if (ret) { break; } if (copy_to_user(buf, smb_data, size)) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_SMBUS_WRITE_BLOCK: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t command; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; command = smbus_info.cmd; buf = smbus_info.buf; size = smbus_info.size; if (size > SSD_SMBUS_BLOCK_MAX) { ret = -EINVAL; break; } if (copy_from_user(smb_data, buf, size)) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ret = ssd_smbus_write_block(dev, saddr, command, size, smb_data); if (ret) { break; } break; } case SSD_CMD_SMBUS_READ_BLOCK: { struct ssd_smbus_op_info smbus_info; uint8_t smb_data[SSD_SMBUS_BLOCK_MAX]; uint8_t saddr; uint8_t command; uint8_t size; if (copy_from_user(&smbus_info, argp, sizeof(struct ssd_smbus_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } saddr = smbus_info.saddr; command = smbus_info.cmd; buf = smbus_info.buf; size = smbus_info.size; if (size > SSD_SMBUS_BLOCK_MAX) { ret = -EINVAL; break; } ret = ssd_smbus_read_block(dev, saddr, command, size, smb_data); if (ret) { break; } if (copy_to_user(buf, smb_data, size)) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_BM_GET_VER: { uint16_t ver; ret = ssd_bm_get_version(dev, &ver); if (ret) { break; } if (copy_to_user(argp, &ver, sizeof(uint16_t))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_BM_GET_NR_CAP: { int nr_cap; ret = ssd_bm_nr_cap(dev, &nr_cap); if (ret) { break; } if (copy_to_user(argp, &nr_cap, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_BM_CAP_LEARNING: { ret = ssd_bm_enter_cap_learning(dev); if (ret) { break; } break; } case SSD_CMD_CAP_LEARN: { uint32_t cap = 0; ret = ssd_cap_learn(dev, &cap); if (ret) { break; } if (copy_to_user(argp, &cap, sizeof(uint32_t))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_CAP_STATUS: { int cap_status = 0; if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { cap_status = 1; } if (copy_to_user(argp, &cap_status, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_RAM_READ: { struct ssd_ram_op_info ram_info; uint64_t ofs; uint32_t length; size_t rlen, len = dev->hw_info.ram_max_len; int ctrl_idx; if (copy_from_user(&ram_info, argp, sizeof(struct ssd_ram_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ofs = ram_info.start; length = ram_info.length; buf = ram_info.buf; ctrl_idx = ram_info.ctrl_idx; if (ofs >= dev->hw_info.ram_size || length > dev->hw_info.ram_size || 0 == length || (ofs + length) > dev->hw_info.ram_size) { ret = -EINVAL; break; } kbuf = kmalloc(len, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } for (rlen=0; rlenhw_info.ram_max_len; int ctrl_idx; if (copy_from_user(&ram_info, argp, sizeof(struct ssd_ram_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ofs = ram_info.start; length = ram_info.length; buf = ram_info.buf; ctrl_idx = ram_info.ctrl_idx; if (ofs >= dev->hw_info.ram_size || length > dev->hw_info.ram_size || 0 == length || (ofs + length) > dev->hw_info.ram_size) { ret = -EINVAL; break; } kbuf = kmalloc(len, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } for (wlen=0; wlenname); ret = -EFAULT; break; } chip_no = flash_info.flash; chip_ce = flash_info.chip; ctrl_idx = flash_info.ctrl_idx; buf = flash_info.buf; length = dev->hw_info.id_size; //kbuf = kmalloc(length, GFP_KERNEL); kbuf = kmalloc(SSD_NAND_ID_BUFF_SZ, GFP_KERNEL); //xx if (!kbuf) { ret = -ENOMEM; break; } memset(kbuf, 0, length); ret = ssd_nand_read_id(dev, kbuf, chip_no, chip_ce, ctrl_idx); if (ret) { kfree(kbuf); break; } if (copy_to_user(buf, kbuf, length)) { kfree(kbuf); ret = -EFAULT; break; } kfree(kbuf); break; } case SSD_CMD_NAND_READ: { //with oob struct ssd_flash_op_info flash_info; uint32_t length; int flash, chip, page, ctrl_idx; int err = 0; if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } flash = flash_info.flash; chip = flash_info.chip; page = flash_info.page; buf = flash_info.buf; ctrl_idx = flash_info.ctrl_idx; length = dev->hw_info.page_size + dev->hw_info.oob_size; kbuf = kmalloc(length, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } err = ret = ssd_nand_read_w_oob(dev, kbuf, flash, chip, page, 1, ctrl_idx); if (ret && (-EIO != ret)) { kfree(kbuf); break; } if (copy_to_user(buf, kbuf, length)) { kfree(kbuf); ret = -EFAULT; break; } ret = err; kfree(kbuf); break; } case SSD_CMD_NAND_WRITE: { struct ssd_flash_op_info flash_info; int flash, chip, page, ctrl_idx; uint32_t length; if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } flash = flash_info.flash; chip = flash_info.chip; page = flash_info.page; buf = flash_info.buf; ctrl_idx = flash_info.ctrl_idx; length = dev->hw_info.page_size + dev->hw_info.oob_size; kbuf = kmalloc(length, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } if (copy_from_user(kbuf, buf, length)) { kfree(kbuf); ret = -EFAULT; break; } ret = ssd_nand_write(dev, kbuf, flash, chip, page, 1, ctrl_idx); if (ret) { kfree(kbuf); break; } kfree(kbuf); break; } case SSD_CMD_NAND_ERASE: { struct ssd_flash_op_info flash_info; int flash, chip, page, ctrl_idx; if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } flash = flash_info.flash; chip = flash_info.chip; page = flash_info.page; ctrl_idx = flash_info.ctrl_idx; if ((page % dev->hw_info.page_count) != 0) { ret = -EINVAL; break; } //hio_warn("erase fs = %llx\n", ofs); ret = ssd_nand_erase(dev, flash, chip, page, ctrl_idx); if (ret) { break; } break; } case SSD_CMD_NAND_READ_EXT: { //ingore EIO struct ssd_flash_op_info flash_info; uint32_t length; int flash, chip, page, ctrl_idx; if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } flash = flash_info.flash; chip = flash_info.chip; page = flash_info.page; buf = flash_info.buf; ctrl_idx = flash_info.ctrl_idx; length = dev->hw_info.page_size + dev->hw_info.oob_size; kbuf = kmalloc(length, GFP_KERNEL); if (!kbuf) { ret = -ENOMEM; break; } ret = ssd_nand_read_w_oob(dev, kbuf, flash, chip, page, 1, ctrl_idx); if (-EIO == ret) { //ingore EIO ret = 0; } if (ret) { kfree(kbuf); break; } if (copy_to_user(buf, kbuf, length)) { kfree(kbuf); ret = -EFAULT; break; } kfree(kbuf); break; } case SSD_CMD_UPDATE_BBT: { struct ssd_flash_op_info flash_info; int ctrl_idx, flash; if (copy_from_user(&flash_info, argp, sizeof(struct ssd_flash_op_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ctrl_idx = flash_info.ctrl_idx; flash = flash_info.flash; ret = ssd_update_bbt(dev, flash, ctrl_idx); if (ret) { break; } break; } case SSD_CMD_CLEAR_ALARM: ssd_clear_alarm(dev); break; case SSD_CMD_SET_ALARM: ssd_set_alarm(dev); break; case SSD_CMD_RESET: ret = ssd_do_reset(dev); break; case SSD_CMD_RELOAD_FW: dev->reload_fw = 1; if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FLAG); } else if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_1_1) { ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FW); } break; case SSD_CMD_UNLOAD_DEV: { if (atomic_read(&dev->refcnt)) { ret = -EBUSY; break; } /* save smart */ ssd_save_smart(dev); ret = ssd_flush(dev); if (ret) { break; } /* cleanup the block device */ if (test_and_clear_bit(SSD_INIT_BD, &dev->state)) { mutex_lock(&dev->gd_mutex); ssd_cleanup_blkdev(dev); mutex_unlock(&dev->gd_mutex); } break; } case SSD_CMD_LOAD_DEV: { if (test_bit(SSD_INIT_BD, &dev->state)) { ret = -EINVAL; break; } ret = ssd_init_smart(dev); if (ret) { hio_warn("%s: init info: failed\n", dev->name); break; } ret = ssd_init_blkdev(dev); if (ret) { hio_warn("%s: register block device: failed\n", dev->name); break; } (void)test_and_set_bit(SSD_INIT_BD, &dev->state); break; } case SSD_CMD_UPDATE_VP: { uint32_t val; uint32_t new_vp, new_vp1 = 0; if (test_bit(SSD_INIT_BD, &dev->state)) { ret = -EINVAL; break; } if (copy_from_user(&new_vp, argp, sizeof(uint32_t))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } if (new_vp > dev->hw_info.max_valid_pages || new_vp <= 0) { ret = -EINVAL; break; } while (new_vp <= dev->hw_info.max_valid_pages) { ssd_reg32_write(dev->ctrlp + SSD_VALID_PAGES_REG, new_vp); msleep(10); val = ssd_reg32_read(dev->ctrlp + SSD_VALID_PAGES_REG); if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { new_vp1 = val & 0x3FF; } else { new_vp1 = val & 0x7FFF; } if (new_vp1 == new_vp) { break; } new_vp++; /*if (new_vp == dev->hw_info.valid_pages) { new_vp++; }*/ } if (new_vp1 != new_vp || new_vp > dev->hw_info.max_valid_pages) { /* restore */ ssd_reg32_write(dev->ctrlp + SSD_VALID_PAGES_REG, dev->hw_info.valid_pages); ret = -EINVAL; break; } if (copy_to_user(argp, &new_vp, sizeof(uint32_t))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ssd_reg32_write(dev->ctrlp + SSD_VALID_PAGES_REG, dev->hw_info.valid_pages); ret = -EFAULT; break; } /* new */ dev->hw_info.valid_pages = new_vp; dev->hw_info.size = (uint64_t)dev->hw_info.valid_pages * dev->hw_info.page_size; dev->hw_info.size *= (dev->hw_info.block_count - dev->hw_info.reserved_blks); dev->hw_info.size *= ((uint64_t)dev->hw_info.nr_data_ch * (uint64_t)dev->hw_info.nr_chip * (uint64_t)dev->hw_info.nr_ctrl); break; } case SSD_CMD_FULL_RESET: { ret = ssd_full_reset(dev); break; } case SSD_CMD_GET_NR_LOG: { if (copy_to_user(argp, &dev->internal_log.nr_log, sizeof(dev->internal_log.nr_log))) { ret = -EFAULT; break; } break; } case SSD_CMD_GET_LOG: { uint32_t length = dev->rom_info.log_sz; buf = argp; if (copy_to_user(buf, dev->internal_log.log, length)) { ret = -EFAULT; break; } break; } case SSD_CMD_LOG_LEVEL: { int level = 0; if (copy_from_user(&level, argp, sizeof(int))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } if (level >= SSD_LOG_NR_LEVEL || level < SSD_LOG_LEVEL_INFO) { level = SSD_LOG_LEVEL_ERR; } //just for showing log, no need to protect log_level = level; break; } case SSD_CMD_OT_PROTECT: { int protect = 0; if (copy_from_user(&protect, argp, sizeof(int))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ssd_set_ot_protect(dev, !!protect); break; } case SSD_CMD_GET_OT_STATUS: { int status = ssd_get_ot_status(dev, &status); if (copy_to_user(argp, &status, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_CLEAR_LOG: { ret = ssd_clear_log(dev); break; } case SSD_CMD_CLEAR_SMART: { ret = ssd_clear_smart(dev); break; } case SSD_CMD_SW_LOG: { struct ssd_sw_log_info sw_log; if (copy_from_user(&sw_log, argp, sizeof(struct ssd_sw_log_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ret = ssd_gen_swlog(dev, sw_log.event, sw_log.data); break; } case SSD_CMD_GET_LABEL: { if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { ret = -EINVAL; break; } if (copy_to_user(argp, &dev->label, sizeof(struct ssd_label))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_VERSION: { struct ssd_version_info ver; mutex_lock(&dev->fw_mutex); ret = __ssd_get_version(dev, &ver); mutex_unlock(&dev->fw_mutex); if (ret) { break; } if (copy_to_user(argp, &ver, sizeof(struct ssd_version_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_TEMPERATURE: { int temp; mutex_lock(&dev->fw_mutex); ret = __ssd_get_temperature(dev, &temp); mutex_unlock(&dev->fw_mutex); if (ret) { break; } if (copy_to_user(argp, &temp, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_BMSTATUS: { int status; mutex_lock(&dev->fw_mutex); if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { status = SSD_BMSTATUS_WARNING; } else { status = SSD_BMSTATUS_OK; } } else if(dev->protocol_info.ver > SSD_PROTOCOL_V3) { ret = __ssd_bm_status(dev, &status); } else { status = SSD_BMSTATUS_OK; } mutex_unlock(&dev->fw_mutex); if (ret) { break; } if (copy_to_user(argp, &status, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_LABEL2: { void *label; int length; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { label = &dev->label; length = sizeof(struct ssd_label); } else { label = &dev->labelv3; length = sizeof(struct ssd_labelv3); } if (copy_to_user(argp, label, length)) { ret = -EFAULT; break; } break; } case SSD_CMD_FLUSH: ret = ssd_flush(dev); if (ret) { hio_warn("%s: ssd_flush: failed\n", dev->name); ret = -EFAULT; break; } break; case SSD_CMD_SAVE_MD: { int save_md = 0; if (copy_from_user(&save_md, argp, sizeof(int))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } dev->save_md = !!save_md; break; } case SSD_CMD_SET_WMODE: { int new_wmode = 0; if (copy_from_user(&new_wmode, argp, sizeof(int))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } ret = __ssd_set_wmode(dev, new_wmode); if (ret) { break; } break; } case SSD_CMD_GET_WMODE: { if (copy_to_user(argp, &dev->wmode, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_GET_USER_WMODE: { if (copy_to_user(argp, &dev->user_wmode, sizeof(int))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } case SSD_CMD_DEBUG: { struct ssd_debug_info db_info; if (!finject) { ret = -EOPNOTSUPP; break; } if (copy_from_user(&db_info, argp, sizeof(struct ssd_debug_info))) { hio_warn("%s: copy_from_user: failed\n", dev->name); ret = -EFAULT; break; } if (db_info.type < SSD_DEBUG_NONE || db_info.type >= SSD_DEBUG_NR) { ret = -EINVAL; break; } /* IO */ if (db_info.type >= SSD_DEBUG_READ_ERR && db_info.type <= SSD_DEBUG_RW_ERR && (db_info.data.loc.off + db_info.data.loc.len) > (dev->hw_info.size >> 9)) { ret = -EINVAL; break; } memcpy(&dev->db_info, &db_info, sizeof(struct ssd_debug_info)); #ifdef SSD_OT_PROTECT /* temperature */ if (db_info.type == SSD_DEBUG_NONE) { ssd_check_temperature(dev, SSD_OT_TEMP); } else if (db_info.type == SSD_DEBUG_LOG) { if (db_info.data.log.event == SSD_LOG_OVER_TEMP) { dev->ot_delay = SSD_OT_DELAY; } else if (db_info.data.log.event == SSD_LOG_NORMAL_TEMP) { dev->ot_delay = 0; } } #endif /* offline */ if (db_info.type == SSD_DEBUG_OFFLINE) { test_and_clear_bit(SSD_ONLINE, &dev->state); } else if (db_info.type == SSD_DEBUG_NONE) { (void)test_and_set_bit(SSD_ONLINE, &dev->state); } /* log */ if (db_info.type == SSD_DEBUG_LOG && dev->event_call && dev->gd) { dev->event_call(dev->gd, db_info.data.log.event, 0); } break; } case SSD_CMD_DRV_PARAM_INFO: { struct ssd_drv_param_info drv_param; memset(&drv_param, 0, sizeof(struct ssd_drv_param_info)); drv_param.mode = mode; drv_param.status_mask = status_mask; drv_param.int_mode = int_mode; drv_param.threaded_irq = threaded_irq; drv_param.log_level = log_level; drv_param.wmode = wmode; drv_param.ot_protect = ot_protect; drv_param.finject = finject; if (copy_to_user(argp, &drv_param, sizeof(struct ssd_drv_param_info))) { hio_warn("%s: copy_to_user: failed\n", dev->name); ret = -EFAULT; break; } break; } default: ret = -EINVAL; break; } return ret; } #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27)) static int ssd_block_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct ssd_device *dev; void __user *argp = (void __user *)arg; int ret = 0; if (!inode) { return -EINVAL; } dev = inode->i_bdev->bd_disk->private_data; if (!dev) { return -EINVAL; } #else static int ssd_block_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { struct ssd_device *dev; void __user *argp = (void __user *)arg; int ret = 0; if (!bdev) { return -EINVAL; } dev = bdev->bd_disk->private_data; if (!dev) { return -EINVAL; } #endif switch (cmd) { case HDIO_GETGEO: { struct hd_geometry geo; geo.cylinders = (dev->hw_info.size & ~0x3f) >> 6; geo.heads = 4; geo.sectors = 16; #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27)) geo.start = get_start_sect(inode->i_bdev); #else geo.start = get_start_sect(bdev); #endif if (copy_to_user(argp, &geo, sizeof(geo))) { ret = -EFAULT; break; } break; } case BLKFLSBUF: ret = ssd_flush(dev); if (ret) { hio_warn("%s: ssd_flush: failed\n", dev->name); ret = -EFAULT; break; } break; default: if (!dev->slave) { ret = ssd_ioctl_common(dev, cmd, arg); } else { ret = -EFAULT; } break; } return ret; } static void ssd_free_dev(struct kref *kref) { struct ssd_device *dev; if (!kref) { return; } dev = container_of(kref, struct ssd_device, kref); put_disk(dev->gd); ssd_put_index(dev->slave, dev->idx); kfree(dev); } static void ssd_put(struct ssd_device *dev) { kref_put(&dev->kref, ssd_free_dev); } static int ssd_get(struct ssd_device *dev) { kref_get(&dev->kref); return 0; } /* block device */ #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27)) static int ssd_block_open(struct inode *inode, struct file *filp) { struct ssd_device *dev; if (!inode) { return -EINVAL; } dev = inode->i_bdev->bd_disk->private_data; if (!dev) { return -EINVAL; } #else static int ssd_block_open(struct block_device *bdev, fmode_t mode) { struct ssd_device *dev; if (!bdev) { return -EINVAL; } dev = bdev->bd_disk->private_data; if (!dev) { return -EINVAL; } #endif /*if (!try_module_get(dev->owner)) return -ENODEV; */ ssd_get(dev); atomic_inc(&dev->refcnt); return 0; } #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27)) static int ssd_block_release(struct inode *inode, struct file *filp) { struct ssd_device *dev; if (!inode) { return -EINVAL; } dev = inode->i_bdev->bd_disk->private_data; if (!dev) { return -EINVAL; } #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(3,9,0)) static int ssd_block_release(struct gendisk *disk, fmode_t mode) { struct ssd_device *dev; if (!disk) { return -EINVAL; } dev = disk->private_data; if (!dev) { return -EINVAL; } #else static void ssd_block_release(struct gendisk *disk, fmode_t mode) { struct ssd_device *dev; if (!disk) { return; } dev = disk->private_data; if (!dev) { return; } #endif atomic_dec(&dev->refcnt); ssd_put(dev); //module_put(dev->owner); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,9,0)) return 0; #endif } static struct block_device_operations ssd_fops = { .owner = THIS_MODULE, .open = ssd_block_open, .release = ssd_block_release, .ioctl = ssd_block_ioctl, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)) .getgeo = ssd_block_getgeo, #endif }; static void ssd_init_trim(ssd_device_t *dev) { #if (defined SSD_TRIM && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32))) if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { return; } queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, dev->rq); #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)) || (defined RHEL_MAJOR && RHEL_MAJOR >= 6)) dev->rq->limits.discard_zeroes_data = 1; dev->rq->limits.discard_alignment = 4096; dev->rq->limits.discard_granularity = 4096; #endif if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2_4) { dev->rq->limits.max_discard_sectors = dev->hw_info.sg_max_sec; } else { dev->rq->limits.max_discard_sectors = (dev->hw_info.sg_max_sec) * (dev->hw_info.cmd_max_sg); } #endif } static void ssd_cleanup_queue(struct ssd_device *dev) { ssd_wait_io(dev); blk_cleanup_queue(dev->rq); dev->rq = NULL; } static int ssd_init_queue(struct ssd_device *dev) { dev->rq = blk_alloc_queue(GFP_KERNEL); if (dev->rq == NULL) { hio_warn("%s: alloc queue: failed\n ", dev->name); goto out_init_queue; } /* must be first */ blk_queue_make_request(dev->rq, ssd_make_request); #if ((LINUX_VERSION_CODE < KERNEL_VERSION(2,6,34)) && !(defined RHEL_MAJOR && RHEL_MAJOR == 6)) blk_queue_max_hw_segments(dev->rq, dev->hw_info.cmd_max_sg); blk_queue_max_phys_segments(dev->rq, dev->hw_info.cmd_max_sg); blk_queue_max_sectors(dev->rq, dev->hw_info.sg_max_sec); #else blk_queue_max_segments(dev->rq, dev->hw_info.cmd_max_sg); blk_queue_max_hw_sectors(dev->rq, dev->hw_info.sg_max_sec); #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)) blk_queue_hardsect_size(dev->rq, 512); #else blk_queue_logical_block_size(dev->rq, 512); #endif /* not work for make_request based drivers(bio) */ blk_queue_max_segment_size(dev->rq, dev->hw_info.sg_max_sec << 9); blk_queue_bounce_limit(dev->rq, BLK_BOUNCE_HIGH); dev->rq->queuedata = dev; #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) blk_queue_issue_flush_fn(dev->rq, ssd_issue_flush_fn); #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)) queue_flag_set_unlocked(QUEUE_FLAG_NONROT, dev->rq); #endif ssd_init_trim(dev); return 0; out_init_queue: return -ENOMEM; } static void ssd_cleanup_blkdev(struct ssd_device *dev) { del_gendisk(dev->gd); } static int ssd_init_blkdev(struct ssd_device *dev) { if (dev->gd) { put_disk(dev->gd); } dev->gd = alloc_disk(ssd_minors); if (!dev->gd) { hio_warn("%s: alloc_disk fail\n", dev->name); goto out_alloc_gd; } dev->gd->major = dev->major; dev->gd->first_minor = dev->idx * ssd_minors; dev->gd->fops = &ssd_fops; dev->gd->queue = dev->rq; dev->gd->private_data = dev; dev->gd->driverfs_dev = &dev->pdev->dev; snprintf (dev->gd->disk_name, sizeof(dev->gd->disk_name), "%s", dev->name); set_capacity(dev->gd, dev->hw_info.size >> 9); add_disk(dev->gd); return 0; out_alloc_gd: return -ENOMEM; } #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,10)) static int ssd_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) #else static long ssd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) #endif { struct ssd_device *dev; if (!file) { return -EINVAL; } dev = file->private_data; if (!dev) { return -EINVAL; } return (long)ssd_ioctl_common(dev, cmd, arg); } static int ssd_open(struct inode *inode, struct file *file) { struct ssd_device *dev = NULL; struct ssd_device *n = NULL; int idx; int ret = -ENODEV; if (!inode || !file) { return -EINVAL; } idx = iminor(inode); list_for_each_entry_safe(dev, n, &ssd_list, list) { if (dev->idx == idx) { ret = 0; break; } } if (ret) { return ret; } file->private_data = dev; ssd_get(dev); return 0; } static int ssd_release(struct inode *inode, struct file *file) { struct ssd_device *dev; if (!file) { return -EINVAL; } dev = file->private_data; if (!dev) { return -EINVAL; } ssd_put(dev); file->private_data = NULL; return 0; } static struct file_operations ssd_cfops = { .owner = THIS_MODULE, .open = ssd_open, .release = ssd_release, #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,10)) .ioctl = ssd_ioctl, #else .unlocked_ioctl = ssd_ioctl, #endif }; static void ssd_cleanup_chardev(struct ssd_device *dev) { if (dev->slave) { return; } #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)) class_simple_device_remove(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx)); devfs_remove("c%s", dev->name); #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,14)) class_device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx)); devfs_remove("c%s", dev->name); #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,17)) class_device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx)); devfs_remove("c%s", dev->name); #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)) class_device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx)); #else device_destroy(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx)); #endif } static int ssd_init_chardev(struct ssd_device *dev) { int ret = 0; if (dev->slave) { return 0; } #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)) ret = devfs_mk_cdev(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), S_IFCHR|S_IRUSR|S_IWUSR, "c%s", dev->name); if (ret) { goto out; } class_simple_device_add(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name); out: #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,14)) ret = devfs_mk_cdev(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), S_IFCHR|S_IRUSR|S_IWUSR, "c%s", dev->name); if (ret) { goto out; } class_device_create(ssd_class, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name); out: #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,17)) ret = devfs_mk_cdev(MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), S_IFCHR|S_IRUSR|S_IWUSR, "c%s", dev->name); if (ret) { goto out; } class_device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name); out: #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)) class_device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name); #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26)) device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), "c%s", dev->name); #elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27)) device_create_drvdata(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name); #else device_create(ssd_class, NULL, MKDEV((dev_t)dev->cmajor, (dev_t)dev->idx), NULL, "c%s", dev->name); #endif return ret; } static int ssd_check_hw(struct ssd_device *dev) { uint32_t test_data = 0x55AA5AA5; uint32_t read_data; ssd_reg32_write(dev->ctrlp + SSD_BRIDGE_TEST_REG, test_data); read_data = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_TEST_REG); if (read_data != ~(test_data)) { //hio_warn("%s: check bridge error: %#x\n", dev->name, read_data); return -1; } return 0; } static int ssd_check_fw(struct ssd_device *dev) { uint32_t val = 0; int i; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) { return 0; } for (i=0; ictrlp + SSD_HW_STATUS_REG); if ((val & 0x1) && ((val >> 8) & 0x1)) { break; } msleep(SSD_INIT_WAIT); } if (!(val & 0x1)) { /* controller fw status */ hio_warn("%s: controller firmware load failed: %#x\n", dev->name, val); return -1; } else if (!((val >> 8) & 0x1)) { /* controller state */ hio_warn("%s: controller state error: %#x\n", dev->name, val); return -1; } val = ssd_reg32_read(dev->ctrlp + SSD_RELOAD_FW_REG); if (val) { dev->reload_fw = 1; } return 0; } static int ssd_init_fw_info(struct ssd_device *dev) { uint32_t val; int ret = 0; val = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_VER_REG); dev->hw_info.bridge_ver = val & 0xFFF; if (dev->hw_info.bridge_ver < SSD_FW_MIN) { hio_warn("%s: bridge firmware version %03X is not supported\n", dev->name, dev->hw_info.bridge_ver); return -EINVAL; } hio_info("%s: bridge firmware version: %03X\n", dev->name, dev->hw_info.bridge_ver); ret = ssd_check_fw(dev); if (ret) { goto out; } out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } static int ssd_check_clock(struct ssd_device *dev) { uint32_t val; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) { return 0; } val = ssd_reg32_read(dev->ctrlp + SSD_HW_STATUS_REG); /* clock status */ if (!((val >> 4 ) & 0x1)) { if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_166M_LOST), &dev->hwmon)) { hio_warn("%s: 166MHz clock losed: %#x\n", dev->name, val); ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val); } ret = -1; } if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { if (!((val >> 5 ) & 0x1)) { if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_166M_SKEW), &dev->hwmon)) { hio_warn("%s: 166MHz clock is skew: %#x\n", dev->name, val); ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val); } ret = -1; } if (!((val >> 6 ) & 0x1)) { if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_156M_LOST), &dev->hwmon)) { hio_warn("%s: 156.25MHz clock lost: %#x\n", dev->name, val); ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val); } ret = -1; } if (!((val >> 7 ) & 0x1)) { if (!test_and_set_bit(SSD_HWMON_CLOCK(SSD_CLOCK_156M_SKEW), &dev->hwmon)) { hio_warn("%s: 156.25MHz clock is skew: %#x\n", dev->name, val); ssd_gen_swlog(dev, SSD_LOG_CLK_FAULT, val); } ret = -1; } } return ret; } static int ssd_check_volt(struct ssd_device *dev) { int i = 0; uint64_t val; uint32_t adc_val; int ret =0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return 0; } for (i=0; ihw_info.nr_ctrl; i++) { /* 1.0v */ if (!test_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V0), &dev->hwmon)) { val = ssd_reg_read(dev->ctrlp + SSD_FPGA_1V0_REG0 + i * SSD_CTRL_REG_ZONE_SZ); adc_val = SSD_FPGA_VOLT_MAX(val); if (adc_val < SSD_FPGA_1V0_ADC_MIN || adc_val > SSD_FPGA_1V0_ADC_MAX) { (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V0), &dev->hwmon); hio_warn("%s: controller %d 1.0V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val)); ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V0, i, adc_val)); ret = -1; } adc_val = SSD_FPGA_VOLT_MIN(val); if (adc_val < SSD_FPGA_1V0_ADC_MIN || adc_val > SSD_FPGA_1V0_ADC_MAX) { (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V0), &dev->hwmon); hio_warn("%s: controller %d 1.0V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val)); ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V0, i, adc_val)); ret = -2; } } /* 1.8v */ if (!test_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V8), &dev->hwmon)) { val = ssd_reg_read(dev->ctrlp + SSD_FPGA_1V8_REG0 + i * SSD_CTRL_REG_ZONE_SZ); adc_val = SSD_FPGA_VOLT_MAX(val); if (adc_val < SSD_FPGA_1V8_ADC_MIN || adc_val > SSD_FPGA_1V8_ADC_MAX) { (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V8), &dev->hwmon); hio_warn("%s: controller %d 1.8V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val)); ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V8, i, adc_val)); ret = -3; } adc_val = SSD_FPGA_VOLT_MIN(val); if (adc_val < SSD_FPGA_1V8_ADC_MIN || adc_val > SSD_FPGA_1V8_ADC_MAX) { (void)test_and_set_bit(SSD_HWMON_FPGA(i, SSD_FPGA_1V8), &dev->hwmon); hio_warn("%s: controller %d 1.8V fault: %d mV.\n", dev->name, i, SSD_FPGA_VOLT(adc_val)); ssd_gen_swlog(dev, SSD_LOG_VOLT_FAULT, SSD_VOLT_LOG_DATA(SSD_FPGA_1V8, i, adc_val)); ret = -4; } } } return ret; } static int ssd_check_reset_sync(struct ssd_device *dev) { uint32_t val; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) { return 0; } val = ssd_reg32_read(dev->ctrlp + SSD_HW_STATUS_REG); if (!((val >> 8) & 0x1)) { /* controller state */ hio_warn("%s: controller state error: %#x\n", dev->name, val); return -1; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return 0; } if (((val >> 9 ) & 0x1)) { hio_warn("%s: controller reset asynchronously: %#x\n", dev->name, val); ssd_gen_swlog(dev, SSD_LOG_CTRL_RST_SYNC, val); return -1; } return 0; } static int ssd_check_hw_bh(struct ssd_device *dev) { int ret; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) { return 0; } /* clock status */ ret = ssd_check_clock(dev); if (ret) { goto out; } out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } static int ssd_check_controller(struct ssd_device *dev) { int ret; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_1_3) { return 0; } /* sync reset */ ret = ssd_check_reset_sync(dev); if (ret) { goto out; } out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } static int ssd_check_controller_bh(struct ssd_device *dev) { uint32_t test_data = 0x55AA5AA5; uint32_t val; int reg_base, reg_sz; int init_wait = 0; int i; int ret = 0; if (mode != SSD_DRV_MODE_STANDARD) { return 0; } /* controller */ val = ssd_reg32_read(dev->ctrlp + SSD_READY_REG); if (val & 0x1) { hio_warn("%s: controller 0 not ready\n", dev->name); return -1; } for (i=0; ihw_info.nr_ctrl; i++) { reg_base = SSD_CTRL_TEST_REG0 + i * SSD_CTRL_TEST_REG_SZ; ssd_reg32_write(dev->ctrlp + reg_base, test_data); val = ssd_reg32_read(dev->ctrlp + reg_base); if (val != ~(test_data)) { hio_warn("%s: check controller %d error: %#x\n", dev->name, i, val); return -1; } } /* clock */ ret = ssd_check_volt(dev); if (ret) { return ret; } /* ddr */ if (dev->protocol_info.ver > SSD_PROTOCOL_V3) { reg_base = SSD_PV3_RAM_STATUS_REG0; reg_sz = SSD_PV3_RAM_STATUS_REG_SZ; for (i=0; ihw_info.nr_ctrl; i++) { check_ram_status: val = ssd_reg32_read(dev->ctrlp + reg_base); if (!((val >> 1) & 0x1)) { init_wait++; if (init_wait <= SSD_RAM_INIT_MAX_WAIT) { msleep(SSD_INIT_WAIT); goto check_ram_status; } else { hio_warn("%s: controller %d ram init failed: %#x\n", dev->name, i, val); ssd_gen_swlog(dev, SSD_LOG_DDR_INIT_ERR, i); return -1; } } reg_base += reg_sz; } } /* ch info */ for (i=0; ictrlp + SSD_CH_INFO_REG); if (!((val >> 31) & 0x1)) { break; } msleep(SSD_INIT_WAIT); } if ((val >> 31) & 0x1) { hio_warn("%s: channel info init failed: %#x\n", dev->name, val); return -1; } return 0; } static int ssd_init_protocol_info(struct ssd_device *dev) { uint32_t val; val = ssd_reg32_read(dev->ctrlp + SSD_PROTOCOL_VER_REG); if (val == (uint32_t)-1) { hio_warn("%s: protocol version error: %#x\n", dev->name, val); return -EINVAL; } dev->protocol_info.ver = val; if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { dev->protocol_info.init_state_reg = SSD_INIT_STATE_REG0; dev->protocol_info.init_state_reg_sz = SSD_INIT_STATE_REG_SZ; dev->protocol_info.chip_info_reg = SSD_CHIP_INFO_REG0; dev->protocol_info.chip_info_reg_sz = SSD_CHIP_INFO_REG_SZ; } else { dev->protocol_info.init_state_reg = SSD_PV3_INIT_STATE_REG0; dev->protocol_info.init_state_reg_sz = SSD_PV3_INIT_STATE_REG_SZ; dev->protocol_info.chip_info_reg = SSD_PV3_CHIP_INFO_REG0; dev->protocol_info.chip_info_reg_sz = SSD_PV3_CHIP_INFO_REG_SZ; } return 0; } static int ssd_init_hw_info(struct ssd_device *dev) { uint64_t val64; uint32_t val; uint32_t nr_ctrl; int ret = 0; /* base info */ val = ssd_reg32_read(dev->ctrlp + SSD_RESP_INFO_REG); dev->hw_info.resp_ptr_sz = 16 * (1U << (val & 0xFF)); dev->hw_info.resp_msg_sz = 16 * (1U << ((val >> 8) & 0xFF)); if (0 == dev->hw_info.resp_ptr_sz || 0 == dev->hw_info.resp_msg_sz) { hio_warn("%s: response info error\n", dev->name); ret = -EINVAL; goto out; } val = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_INFO_REG); dev->hw_info.cmd_fifo_sz = 1U << ((val >> 4) & 0xF); dev->hw_info.cmd_max_sg = 1U << ((val >> 8) & 0xF); dev->hw_info.sg_max_sec = 1U << ((val >> 12) & 0xF); dev->hw_info.cmd_fifo_sz_mask = dev->hw_info.cmd_fifo_sz - 1; if (0 == dev->hw_info.cmd_fifo_sz || 0 == dev->hw_info.cmd_max_sg || 0 == dev->hw_info.sg_max_sec) { hio_warn("%s: cmd info error\n", dev->name); ret = -EINVAL; goto out; } /* check hw */ if (ssd_check_hw_bh(dev)) { hio_warn("%s: check hardware status failed\n", dev->name); ret = -EINVAL; goto out; } if (ssd_check_controller(dev)) { hio_warn("%s: check controller state failed\n", dev->name); ret = -EINVAL; goto out; } /* nr controller : read again*/ val = ssd_reg32_read(dev->ctrlp + SSD_BRIDGE_INFO_REG); dev->hw_info.nr_ctrl = (val >> 16) & 0xF; /* nr ctrl configured */ nr_ctrl = (val >> 20) & 0xF; if (0 == dev->hw_info.nr_ctrl) { hio_warn("%s: nr controller error: %u\n", dev->name, dev->hw_info.nr_ctrl); ret = -EINVAL; goto out; } else if (0 != nr_ctrl && nr_ctrl != dev->hw_info.nr_ctrl) { hio_warn("%s: nr controller error: configured %u but found %u\n", dev->name, nr_ctrl, dev->hw_info.nr_ctrl); if (mode <= SSD_DRV_MODE_STANDARD) { ret = -EINVAL; goto out; } } if (ssd_check_controller_bh(dev)) { hio_warn("%s: check controller failed\n", dev->name); ret = -EINVAL; goto out; } val = ssd_reg32_read(dev->ctrlp + SSD_PCB_VER_REG); dev->hw_info.pcb_ver = (uint8_t) ((val >> 4) & 0xF) + 'A' -1; if ((val & 0xF) != 0xF) { dev->hw_info.upper_pcb_ver = (uint8_t) (val & 0xF) + 'A' -1; } if (dev->hw_info.pcb_ver < 'A' || (0 != dev->hw_info.upper_pcb_ver && dev->hw_info.upper_pcb_ver < 'A')) { hio_warn("%s: PCB version error: %#x %#x\n", dev->name, dev->hw_info.pcb_ver, dev->hw_info.upper_pcb_ver); ret = -EINVAL; goto out; } /* channel info */ if (mode <= SSD_DRV_MODE_DEBUG) { val = ssd_reg32_read(dev->ctrlp + SSD_CH_INFO_REG); dev->hw_info.nr_data_ch = val & 0xFF; dev->hw_info.nr_ch = dev->hw_info.nr_data_ch + ((val >> 8) & 0xFF); dev->hw_info.nr_chip = (val >> 16) & 0xFF; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { dev->hw_info.max_ch = 1; while (dev->hw_info.max_ch < dev->hw_info.nr_ch) dev->hw_info.max_ch <<= 1; } else { /* set max channel 32 */ dev->hw_info.max_ch = 32; } if (0 == dev->hw_info.nr_chip) { //for debug mode dev->hw_info.nr_chip = 1; } //xx dev->hw_info.id_size = SSD_NAND_ID_SZ; dev->hw_info.max_ce = SSD_NAND_MAX_CE; if (0 == dev->hw_info.nr_data_ch || 0 == dev->hw_info.nr_ch || 0 == dev->hw_info.nr_chip) { hio_warn("%s: channel info error: data_ch %u ch %u chip %u\n", dev->name, dev->hw_info.nr_data_ch, dev->hw_info.nr_ch, dev->hw_info.nr_chip); ret = -EINVAL; goto out; } } /* ram info */ if (mode <= SSD_DRV_MODE_DEBUG) { val = ssd_reg32_read(dev->ctrlp + SSD_RAM_INFO_REG); dev->hw_info.ram_size = 0x4000000ull * (1ULL << (val & 0xF)); dev->hw_info.ram_align = 1U << ((val >> 12) & 0xF); if (dev->hw_info.ram_align < SSD_RAM_ALIGN) { if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { dev->hw_info.ram_align = SSD_RAM_ALIGN; } else { hio_warn("%s: ram align error: %u\n", dev->name, dev->hw_info.ram_align); ret = -EINVAL; goto out; } } dev->hw_info.ram_max_len = 0x1000 * (1U << ((val >> 16) & 0xF)); if (0 == dev->hw_info.ram_size || 0 == dev->hw_info.ram_align || 0 == dev->hw_info.ram_max_len || dev->hw_info.ram_align > dev->hw_info.ram_max_len) { hio_warn("%s: ram info error\n", dev->name); ret = -EINVAL; goto out; } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { dev->hw_info.log_sz = SSD_LOG_MAX_SZ; } else { val = ssd_reg32_read(dev->ctrlp + SSD_LOG_INFO_REG); dev->hw_info.log_sz = 0x1000 * (1U << (val & 0xFF)); } if (0 == dev->hw_info.log_sz) { hio_warn("%s: log size error\n", dev->name); ret = -EINVAL; goto out; } val = ssd_reg32_read(dev->ctrlp + SSD_BBT_BASE_REG); dev->hw_info.bbt_base = 0x40000ull * (val & 0xFFFF); dev->hw_info.bbt_size = 0x40000 * (((val >> 16) & 0xFFFF) + 1) / (dev->hw_info.max_ch * dev->hw_info.nr_chip); if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { if (dev->hw_info.bbt_base > dev->hw_info.ram_size || 0 == dev->hw_info.bbt_size) { hio_warn("%s: bbt info error\n", dev->name); ret = -EINVAL; goto out; } } val = ssd_reg32_read(dev->ctrlp + SSD_ECT_BASE_REG); dev->hw_info.md_base = 0x40000ull * (val & 0xFFFF); if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { dev->hw_info.md_size = 0x40000 * (((val >> 16) & 0xFFF) + 1) / (dev->hw_info.max_ch * dev->hw_info.nr_chip); } else { dev->hw_info.md_size = 0x40000 * (((val >> 16) & 0xFFF) + 1) / (dev->hw_info.nr_chip); } dev->hw_info.md_entry_sz = 8 * (1U << ((val >> 28) & 0xF)); if (dev->protocol_info.ver >= SSD_PROTOCOL_V3) { if (dev->hw_info.md_base > dev->hw_info.ram_size || 0 == dev->hw_info.md_size || 0 == dev->hw_info.md_entry_sz || dev->hw_info.md_entry_sz > dev->hw_info.md_size) { hio_warn("%s: md info error\n", dev->name); ret = -EINVAL; goto out; } } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { dev->hw_info.nand_wbuff_base = dev->hw_info.ram_size + 1; } else { val = ssd_reg32_read(dev->ctrlp + SSD_NAND_BUFF_BASE); dev->hw_info.nand_wbuff_base = 0x8000ull * val; } } /* flash info */ if (mode <= SSD_DRV_MODE_DEBUG) { if (dev->hw_info.nr_ctrl > 1) { val = ssd_reg32_read(dev->ctrlp + SSD_CTRL_VER_REG); dev->hw_info.ctrl_ver = val & 0xFFF; hio_info("%s: controller firmware version: %03X\n", dev->name, dev->hw_info.ctrl_ver); } val64 = ssd_reg_read(dev->ctrlp + SSD_FLASH_INFO_REG0); dev->hw_info.nand_vendor_id = ((val64 >> 56) & 0xFF); dev->hw_info.nand_dev_id = ((val64 >> 48) & 0xFF); dev->hw_info.block_count = (((val64 >> 32) & 0xFFFF) + 1); dev->hw_info.page_count = ((val64>>16) & 0xFFFF); dev->hw_info.page_size = (val64 & 0xFFFF); val = ssd_reg32_read(dev->ctrlp + SSD_BB_INFO_REG); dev->hw_info.bbf_pages = val & 0xFF; dev->hw_info.bbf_seek = (val >> 8) & 0x1; if (0 == dev->hw_info.block_count || 0 == dev->hw_info.page_count || 0 == dev->hw_info.page_size || dev->hw_info.block_count > INT_MAX) { hio_warn("%s: flash info error\n", dev->name); ret = -EINVAL; goto out; } //xx dev->hw_info.oob_size = SSD_NAND_OOB_SZ; //(dev->hw_info.page_size) >> 5; val = ssd_reg32_read(dev->ctrlp + SSD_VALID_PAGES_REG); if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { dev->hw_info.valid_pages = val & 0x3FF; dev->hw_info.max_valid_pages = (val>>20) & 0x3FF; } else { dev->hw_info.valid_pages = val & 0x7FFF; dev->hw_info.max_valid_pages = (val>>15) & 0x7FFF; } if (0 == dev->hw_info.valid_pages || 0 == dev->hw_info.max_valid_pages || dev->hw_info.valid_pages > dev->hw_info.max_valid_pages || dev->hw_info.max_valid_pages > dev->hw_info.page_count) { hio_warn("%s: valid page info error: valid_pages %d, max_valid_pages %d\n", dev->name, dev->hw_info.valid_pages, dev->hw_info.max_valid_pages); ret = -EINVAL; goto out; } val = ssd_reg32_read(dev->ctrlp + SSD_RESERVED_BLKS_REG); dev->hw_info.reserved_blks = val & 0xFFFF; dev->hw_info.md_reserved_blks = (val >> 16) & 0xFF; if (dev->protocol_info.ver <= SSD_PROTOCOL_V3) { dev->hw_info.md_reserved_blks = SSD_BBT_RESERVED; } if (dev->hw_info.reserved_blks > dev->hw_info.block_count || dev->hw_info.md_reserved_blks > dev->hw_info.block_count) { hio_warn("%s: reserved blocks info error: reserved_blks %d, md_reserved_blks %d\n", dev->name, dev->hw_info.reserved_blks, dev->hw_info.md_reserved_blks); ret = -EINVAL; goto out; } } /* size */ if (mode < SSD_DRV_MODE_DEBUG) { dev->hw_info.size = (uint64_t)dev->hw_info.valid_pages * dev->hw_info.page_size; dev->hw_info.size *= (dev->hw_info.block_count - dev->hw_info.reserved_blks); dev->hw_info.size *= ((uint64_t)dev->hw_info.nr_data_ch * (uint64_t)dev->hw_info.nr_chip * (uint64_t)dev->hw_info.nr_ctrl); } /* extend hardware info */ val = ssd_reg32_read(dev->ctrlp + SSD_PCB_VER_REG); dev->hw_info_ext.board_type = (val >> 24) & 0xF; dev->hw_info_ext.form_factor = SSD_FORM_FACTOR_FHHL; if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2_1) { dev->hw_info_ext.form_factor = (val >> 31) & 0x1; } /* dev->hw_info_ext.cap_type = (val >> 28) & 0x3; if (SSD_BM_CAP_VINA != dev->hw_info_ext.cap_type && SSD_BM_CAP_JH != dev->hw_info_ext.cap_type) { dev->hw_info_ext.cap_type = SSD_BM_CAP_VINA; }*/ /* power loss protect */ val = ssd_reg32_read(dev->ctrlp + SSD_PLP_INFO_REG); dev->hw_info_ext.plp_type = (val & 0x3); if (dev->protocol_info.ver >= SSD_PROTOCOL_V3_2) { /* 3 or 4 cap */ dev->hw_info_ext.cap_type = ((val >> 2)& 0x1); } /* work mode */ val = ssd_reg32_read(dev->ctrlp + SSD_CH_INFO_REG); dev->hw_info_ext.work_mode = (val >> 25) & 0x1; out: /* skip error if not in standard mode */ if (mode != SSD_DRV_MODE_STANDARD) { ret = 0; } return ret; } static void ssd_cleanup_response(struct ssd_device *dev) { int resp_msg_sz = dev->hw_info.resp_msg_sz * dev->hw_info.cmd_fifo_sz * SSD_MSIX_VEC; int resp_ptr_sz = dev->hw_info.resp_ptr_sz * SSD_MSIX_VEC; pci_free_consistent(dev->pdev, resp_ptr_sz, dev->resp_ptr_base, dev->resp_ptr_base_dma); pci_free_consistent(dev->pdev, resp_msg_sz, dev->resp_msg_base, dev->resp_msg_base_dma); } static int ssd_init_response(struct ssd_device *dev) { int resp_msg_sz = dev->hw_info.resp_msg_sz * dev->hw_info.cmd_fifo_sz * SSD_MSIX_VEC; int resp_ptr_sz = dev->hw_info.resp_ptr_sz * SSD_MSIX_VEC; dev->resp_msg_base = pci_alloc_consistent(dev->pdev, resp_msg_sz, &(dev->resp_msg_base_dma)); if (!dev->resp_msg_base) { hio_warn("%s: unable to allocate resp msg DMA buffer\n", dev->name); goto out_alloc_resp_msg; } memset(dev->resp_msg_base, 0xFF, resp_msg_sz); dev->resp_ptr_base = pci_alloc_consistent(dev->pdev, resp_ptr_sz, &(dev->resp_ptr_base_dma)); if (!dev->resp_ptr_base){ hio_warn("%s: unable to allocate resp ptr DMA buffer\n", dev->name); goto out_alloc_resp_ptr; } memset(dev->resp_ptr_base, 0, resp_ptr_sz); dev->resp_idx = *(uint32_t *)(dev->resp_ptr_base) = dev->hw_info.cmd_fifo_sz * 2 - 1; ssd_reg_write(dev->ctrlp + SSD_RESP_FIFO_REG, dev->resp_msg_base_dma); ssd_reg_write(dev->ctrlp + SSD_RESP_PTR_REG, dev->resp_ptr_base_dma); return 0; out_alloc_resp_ptr: pci_free_consistent(dev->pdev, resp_msg_sz, dev->resp_msg_base, dev->resp_msg_base_dma); out_alloc_resp_msg: return -ENOMEM; } static int ssd_cleanup_cmd(struct ssd_device *dev) { int msg_sz = ALIGN(sizeof(struct ssd_rw_msg) + (dev->hw_info.cmd_max_sg - 1) * sizeof(struct ssd_sg_entry), SSD_DMA_ALIGN); int i; for (i=0; i<(int)dev->hw_info.cmd_fifo_sz; i++) { kfree(dev->cmd[i].sgl); } kfree(dev->cmd); pci_free_consistent(dev->pdev, (msg_sz * dev->hw_info.cmd_fifo_sz), dev->msg_base, dev->msg_base_dma); return 0; } static int ssd_init_cmd(struct ssd_device *dev) { int sgl_sz = sizeof(struct scatterlist) * dev->hw_info.cmd_max_sg; int cmd_sz = sizeof(struct ssd_cmd) * dev->hw_info.cmd_fifo_sz; int msg_sz = ALIGN(sizeof(struct ssd_rw_msg) + (dev->hw_info.cmd_max_sg - 1) * sizeof(struct ssd_sg_entry), SSD_DMA_ALIGN); int i; spin_lock_init(&dev->cmd_lock); dev->msg_base = pci_alloc_consistent(dev->pdev, (msg_sz * dev->hw_info.cmd_fifo_sz), &dev->msg_base_dma); if (!dev->msg_base) { hio_warn("%s: can not alloc cmd msg\n", dev->name); goto out_alloc_msg; } dev->cmd = kmalloc(cmd_sz, GFP_KERNEL); if (!dev->cmd) { hio_warn("%s: can not alloc cmd\n", dev->name); goto out_alloc_cmd; } memset(dev->cmd, 0, cmd_sz); for (i=0; i<(int)dev->hw_info.cmd_fifo_sz; i++) { dev->cmd[i].sgl = kmalloc(sgl_sz, GFP_KERNEL); if (!dev->cmd[i].sgl) { hio_warn("%s: can not alloc cmd sgl %d\n", dev->name, i); goto out_alloc_sgl; } dev->cmd[i].msg = dev->msg_base + (msg_sz * i); dev->cmd[i].msg_dma = dev->msg_base_dma + ((dma_addr_t)msg_sz * i); dev->cmd[i].dev = dev; dev->cmd[i].tag = i; dev->cmd[i].flag = 0; INIT_LIST_HEAD(&dev->cmd[i].list); } if (dev->protocol_info.ver < SSD_PROTOCOL_V3) { dev->scmd = ssd_dispatch_cmd; } else { ssd_reg_write(dev->ctrlp + SSD_MSG_BASE_REG, dev->msg_base_dma); if (finject) { dev->scmd = ssd_send_cmd_db; } else { dev->scmd = ssd_send_cmd; } } return 0; out_alloc_sgl: for (i--; i>=0; i--) { kfree(dev->cmd[i].sgl); } kfree(dev->cmd); out_alloc_cmd: pci_free_consistent(dev->pdev, (msg_sz * dev->hw_info.cmd_fifo_sz), dev->msg_base, dev->msg_base_dma); out_alloc_msg: return -ENOMEM; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)) static irqreturn_t ssd_interrupt_check(int irq, void *dev_id) { struct ssd_queue *queue = (struct ssd_queue *)dev_id; if (*(uint32_t *)queue->resp_ptr == queue->resp_idx) { return IRQ_NONE; } return IRQ_WAKE_THREAD; } static irqreturn_t ssd_interrupt_threaded(int irq, void *dev_id) { struct ssd_queue *queue = (struct ssd_queue *)dev_id; struct ssd_device *dev = (struct ssd_device *)queue->dev; struct ssd_cmd *cmd; union ssd_response_msq __msg; union ssd_response_msq *msg = &__msg; uint64_t *u64_msg; uint32_t resp_idx = queue->resp_idx; uint32_t new_resp_idx = *(uint32_t *)queue->resp_ptr; uint32_t end_resp_idx; if (unlikely(resp_idx == new_resp_idx)) { return IRQ_NONE; } end_resp_idx = new_resp_idx & queue->resp_idx_mask; do { resp_idx = (resp_idx + 1) & queue->resp_idx_mask; /* the resp msg */ u64_msg = (uint64_t *)(queue->resp_msg + queue->resp_msg_sz * resp_idx); msg->u64_msg = *u64_msg; if (unlikely(msg->u64_msg == (uint64_t)(-1))) { hio_err("%s: empty resp msg: queue %d idx %u\n", dev->name, queue->idx, resp_idx); continue; } /* clear the resp msg */ *u64_msg = (uint64_t)(-1); cmd = &queue->cmd[msg->resp_msg.tag]; /*if (unlikely(!cmd->bio)) { printk(KERN_WARNING "%s: unknown tag %d fun %#x\n", dev->name, msg->resp_msg.tag, msg->resp_msg.fun); continue; }*/ if(unlikely(msg->resp_msg.status & (uint32_t)status_mask)) { cmd->errors = -EIO; } else { cmd->errors = 0; } cmd->nr_log = msg->log_resp_msg.nr_log; ssd_done(cmd); if (unlikely(msg->resp_msg.fun != SSD_FUNC_READ_LOG && msg->resp_msg.log > 0)) { (void)test_and_set_bit(SSD_LOG_HW, &dev->state); if (test_bit(SSD_INIT_WORKQ, &dev->state)) { queue_work(dev->workq, &dev->log_work); } } if (unlikely(msg->resp_msg.status)) { if (msg->resp_msg.fun == SSD_FUNC_READ || msg->resp_msg.fun == SSD_FUNC_WRITE) { hio_err("%s: I/O error %d: tag %d fun %#x\n", dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun); /* alarm led */ ssd_set_alarm(dev); queue->io_stat.nr_rwerr++; ssd_gen_swlog(dev, SSD_LOG_EIO, msg->u32_msg[0]); } else { hio_info("%s: CMD error %d: tag %d fun %#x\n", dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun); ssd_gen_swlog(dev, SSD_LOG_ECMD, msg->u32_msg[0]); } queue->io_stat.nr_ioerr++; } if (msg->resp_msg.fun == SSD_FUNC_READ || msg->resp_msg.fun == SSD_FUNC_NAND_READ_WOOB || msg->resp_msg.fun == SSD_FUNC_NAND_READ) { queue->ecc_info.bitflip[msg->resp_msg.bitflip]++; } }while (resp_idx != end_resp_idx); queue->resp_idx = new_resp_idx; return IRQ_HANDLED; } #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) static irqreturn_t ssd_interrupt(int irq, void *dev_id, struct pt_regs *regs) #else static irqreturn_t ssd_interrupt(int irq, void *dev_id) #endif { struct ssd_queue *queue = (struct ssd_queue *)dev_id; struct ssd_device *dev = (struct ssd_device *)queue->dev; struct ssd_cmd *cmd; union ssd_response_msq __msg; union ssd_response_msq *msg = &__msg; uint64_t *u64_msg; uint32_t resp_idx = queue->resp_idx; uint32_t new_resp_idx = *(uint32_t *)queue->resp_ptr; uint32_t end_resp_idx; if (unlikely(resp_idx == new_resp_idx)) { return IRQ_NONE; } #if (defined SSD_ESCAPE_IRQ) if (SSD_INT_MSIX != dev->int_mode) { dev->irq_cpu = smp_processor_id(); } #endif end_resp_idx = new_resp_idx & queue->resp_idx_mask; do { resp_idx = (resp_idx + 1) & queue->resp_idx_mask; /* the resp msg */ u64_msg = (uint64_t *)(queue->resp_msg + queue->resp_msg_sz * resp_idx); msg->u64_msg = *u64_msg; if (unlikely(msg->u64_msg == (uint64_t)(-1))) { hio_err("%s: empty resp msg: queue %d idx %u\n", dev->name, queue->idx, resp_idx); continue; } /* clear the resp msg */ *u64_msg = (uint64_t)(-1); cmd = &queue->cmd[msg->resp_msg.tag]; /*if (unlikely(!cmd->bio)) { printk(KERN_WARNING "%s: unknown tag %d fun %#x\n", dev->name, msg->resp_msg.tag, msg->resp_msg.fun); continue; }*/ if(unlikely(msg->resp_msg.status & (uint32_t)status_mask)) { cmd->errors = -EIO; } else { cmd->errors = 0; } cmd->nr_log = msg->log_resp_msg.nr_log; ssd_done_bh(cmd); if (unlikely(msg->resp_msg.fun != SSD_FUNC_READ_LOG && msg->resp_msg.log > 0)) { (void)test_and_set_bit(SSD_LOG_HW, &dev->state); if (test_bit(SSD_INIT_WORKQ, &dev->state)) { queue_work(dev->workq, &dev->log_work); } } if (unlikely(msg->resp_msg.status)) { if (msg->resp_msg.fun == SSD_FUNC_READ || msg->resp_msg.fun == SSD_FUNC_WRITE) { hio_err("%s: I/O error %d: tag %d fun %#x\n", dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun); /* alarm led */ ssd_set_alarm(dev); queue->io_stat.nr_rwerr++; ssd_gen_swlog(dev, SSD_LOG_EIO, msg->u32_msg[0]); } else { hio_info("%s: CMD error %d: tag %d fun %#x\n", dev->name, msg->resp_msg.status, msg->resp_msg.tag, msg->resp_msg.fun); ssd_gen_swlog(dev, SSD_LOG_ECMD, msg->u32_msg[0]); } queue->io_stat.nr_ioerr++; } if (msg->resp_msg.fun == SSD_FUNC_READ || msg->resp_msg.fun == SSD_FUNC_NAND_READ_WOOB || msg->resp_msg.fun == SSD_FUNC_NAND_READ) { queue->ecc_info.bitflip[msg->resp_msg.bitflip]++; } }while (resp_idx != end_resp_idx); queue->resp_idx = new_resp_idx; return IRQ_HANDLED; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) static irqreturn_t ssd_interrupt_legacy(int irq, void *dev_id, struct pt_regs *regs) #else static irqreturn_t ssd_interrupt_legacy(int irq, void *dev_id) #endif { irqreturn_t ret; struct ssd_queue *queue = (struct ssd_queue *)dev_id; struct ssd_device *dev = (struct ssd_device *)queue->dev; #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) ret = ssd_interrupt(irq, dev_id, regs); #else ret = ssd_interrupt(irq, dev_id); #endif /* clear intr */ if (IRQ_HANDLED == ret) { ssd_reg32_write(dev->ctrlp + SSD_CLEAR_INTR_REG, 1); } return ret; } static void ssd_reset_resp_ptr(struct ssd_device *dev) { int i; for (i=0; inr_queue; i++) { *(uint32_t *)dev->queue[i].resp_ptr = dev->queue[i].resp_idx = (dev->hw_info.cmd_fifo_sz * 2) - 1; } } static void ssd_free_irq(struct ssd_device *dev) { int i; #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6)) if (SSD_INT_MSIX == dev->int_mode) { for (i=0; inr_queue; i++) { irq_set_affinity_hint(dev->entry[i].vector, NULL); } } #endif for (i=0; inr_queue; i++) { free_irq(dev->entry[i].vector, &dev->queue[i]); } if (SSD_INT_MSIX == dev->int_mode) { pci_disable_msix(dev->pdev); } else if (SSD_INT_MSI == dev->int_mode) { pci_disable_msi(dev->pdev); } } static int ssd_init_irq(struct ssd_device *dev) { #if (!defined MODULE) && (defined SSD_MSIX_AFFINITY_FORCE) const struct cpumask *cpu_mask; static int cpu_affinity = 0; #endif #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6)) const struct cpumask *mask; static int cpu = 0; int j; #endif int i; unsigned long flags = 0; int ret = 0; ssd_reg32_write(dev->ctrlp + SSD_INTR_INTERVAL_REG, 0x800); #ifdef SSD_ESCAPE_IRQ dev->irq_cpu = -1; #endif if (int_mode >= SSD_INT_MSIX && pci_find_capability(dev->pdev, PCI_CAP_ID_MSIX)) { dev->nr_queue = SSD_MSIX_VEC; for (i=0; inr_queue; i++) { dev->entry[i].entry = i; } for (;;) { ret = pci_enable_msix(dev->pdev, dev->entry, dev->nr_queue); if (ret == 0) { break; } else if (ret > 0) { dev->nr_queue = ret; } else { hio_warn("%s: can not enable msix\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out; } } #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6)) mask = (dev_to_node(&dev->pdev->dev) == -1) ? cpu_online_mask : cpumask_of_node(dev_to_node(&dev->pdev->dev)); if ((0 == cpu) || (!cpumask_intersects(mask, cpumask_of(cpu)))) { cpu = cpumask_first(mask); } for (i=0; inr_queue; i++) { irq_set_affinity_hint(dev->entry[i].vector, cpumask_of(cpu)); cpu = cpumask_next(cpu, mask); if (cpu >= nr_cpu_ids) { cpu = cpumask_first(mask); } } #endif dev->int_mode = SSD_INT_MSIX; } else if (int_mode >= SSD_INT_MSI && pci_find_capability(dev->pdev, PCI_CAP_ID_MSI)) { ret = pci_enable_msi(dev->pdev); if (ret) { hio_warn("%s: can not enable msi\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out; } dev->nr_queue = 1; dev->entry[0].vector = dev->pdev->irq; dev->int_mode = SSD_INT_MSI; } else { dev->nr_queue = 1; dev->entry[0].vector = dev->pdev->irq; dev->int_mode = SSD_INT_LEGACY; } for (i=0; inr_queue; i++) { if (dev->nr_queue > 1) { snprintf(dev->queue[i].name, SSD_QUEUE_NAME_LEN, "%s_e100-%d", dev->name, i); } else { snprintf(dev->queue[i].name, SSD_QUEUE_NAME_LEN, "%s_e100", dev->name); } dev->queue[i].dev = dev; dev->queue[i].idx = i; dev->queue[i].resp_idx = (dev->hw_info.cmd_fifo_sz * 2) - 1; dev->queue[i].resp_idx_mask = dev->hw_info.cmd_fifo_sz - 1; dev->queue[i].resp_msg_sz = dev->hw_info.resp_msg_sz; dev->queue[i].resp_msg = dev->resp_msg_base + dev->hw_info.resp_msg_sz * dev->hw_info.cmd_fifo_sz * i; dev->queue[i].resp_ptr = dev->resp_ptr_base + dev->hw_info.resp_ptr_sz * i; *(uint32_t *)dev->queue[i].resp_ptr = dev->queue[i].resp_idx; dev->queue[i].cmd = dev->cmd; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)) flags = IRQF_SHARED; #else flags = SA_SHIRQ; #endif for (i=0; inr_queue; i++) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)) if (threaded_irq) { ret = request_threaded_irq(dev->entry[i].vector, ssd_interrupt_check, ssd_interrupt_threaded, flags, dev->queue[i].name, &dev->queue[i]); } else if (dev->int_mode == SSD_INT_LEGACY) { ret = request_irq(dev->entry[i].vector, &ssd_interrupt_legacy, flags, dev->queue[i].name, &dev->queue[i]); } else { ret = request_irq(dev->entry[i].vector, &ssd_interrupt, flags, dev->queue[i].name, &dev->queue[i]); } #else if (dev->int_mode == SSD_INT_LEGACY) { ret = request_irq(dev->entry[i].vector, &ssd_interrupt_legacy, flags, dev->queue[i].name, &dev->queue[i]); } else { ret = request_irq(dev->entry[i].vector, &ssd_interrupt, flags, dev->queue[i].name, &dev->queue[i]); } #endif if (ret) { hio_warn("%s: request irq failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_request_irq; } #if (!defined MODULE) && (defined SSD_MSIX_AFFINITY_FORCE) cpu_mask = (dev_to_node(&dev->pdev->dev) == -1) ? cpu_online_mask : cpumask_of_node(dev_to_node(&dev->pdev->dev)); if (SSD_INT_MSIX == dev->int_mode) { if ((0 == cpu_affinity) || (!cpumask_intersects(mask, cpumask_of(cpu_affinity)))) { cpu_affinity = cpumask_first(cpu_mask); } irq_set_affinity(dev->entry[i].vector, cpumask_of(cpu_affinity)); cpu_affinity = cpumask_next(cpu_affinity, cpu_mask); if (cpu_affinity >= nr_cpu_ids) { cpu_affinity = cpumask_first(cpu_mask); } } #endif } return ret; out_request_irq: #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) || (defined RHEL_MAJOR && RHEL_MAJOR == 6)) if (SSD_INT_MSIX == dev->int_mode) { for (j=0; jnr_queue; j++) { irq_set_affinity_hint(dev->entry[j].vector, NULL); } } #endif for (i--; i>=0; i--) { free_irq(dev->entry[i].vector, &dev->queue[i]); } if (SSD_INT_MSIX == dev->int_mode) { pci_disable_msix(dev->pdev); } else if (SSD_INT_MSI == dev->int_mode) { pci_disable_msi(dev->pdev); } out: return ret; } static void ssd_initial_log(struct ssd_device *dev) { uint32_t val; uint32_t speed, width; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return; } val = ssd_reg32_read(dev->ctrlp + SSD_POWER_ON_REG); if (val) { ssd_gen_swlog(dev, SSD_LOG_POWER_ON, dev->hw_info.bridge_ver); } val = ssd_reg32_read(dev->ctrlp + SSD_PCIE_LINKSTATUS_REG); speed = val & 0xF; width = (val >> 4)& 0x3F; if (0x1 == speed) { hio_info("%s: PCIe: 2.5GT/s, x%u\n", dev->name, width); } else if (0x2 == speed) { hio_info("%s: PCIe: 5GT/s, x%u\n", dev->name, width); } else { hio_info("%s: PCIe: unknown GT/s, x%u\n", dev->name, width); } ssd_gen_swlog(dev, SSD_LOG_PCIE_LINK_STATUS, val); return; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) static void ssd_hwmon_worker(void *data) { struct ssd_device *dev = (struct ssd_device *)data; #else static void ssd_hwmon_worker(struct work_struct *work) { struct ssd_device *dev = container_of(work, struct ssd_device, hwmon_work); #endif if (ssd_check_hw(dev)) { //hio_err("%s: check hardware failed\n", dev->name); return; } ssd_check_clock(dev); ssd_check_volt(dev); ssd_mon_boardvolt(dev); } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) static void ssd_tempmon_worker(void *data) { struct ssd_device *dev = (struct ssd_device *)data; #else static void ssd_tempmon_worker(struct work_struct *work) { struct ssd_device *dev = container_of(work, struct ssd_device, tempmon_work); #endif if (ssd_check_hw(dev)) { //hio_err("%s: check hardware failed\n", dev->name); return; } ssd_mon_temp(dev); } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) static void ssd_capmon_worker(void *data) { struct ssd_device *dev = (struct ssd_device *)data; #else static void ssd_capmon_worker(struct work_struct *work) { struct ssd_device *dev = container_of(work, struct ssd_device, capmon_work); #endif uint32_t cap = 0; uint32_t cap_threshold = SSD_PL_CAP_THRESHOLD; int ret = 0; if (dev->protocol_info.ver < SSD_PROTOCOL_V3_2) { return; } if (dev->hw_info_ext.form_factor == SSD_FORM_FACTOR_FHHL && dev->hw_info.pcb_ver < 'B') { return; } /* fault before? */ if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { ret = ssd_check_pl_cap_fast(dev); if (ret) { return; } } /* learn */ ret = ssd_do_cap_learn(dev, &cap); if (ret) { hio_err("%s: cap learn failed\n", dev->name); ssd_gen_swlog(dev, SSD_LOG_CAP_LEARN_FAULT, 0); return; } ssd_gen_swlog(dev, SSD_LOG_CAP_STATUS, cap); if (SSD_PL_CAP_CP == dev->hw_info_ext.cap_type) { cap_threshold = SSD_PL_CAP_CP_THRESHOLD; } //use the fw event id? if (cap < cap_threshold) { if (!test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_BATTERY_FAULT, 0); } } else if (cap >= (cap_threshold + SSD_PL_CAP_THRESHOLD_HYST)) { if (test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon)) { ssd_gen_swlog(dev, SSD_LOG_BATTERY_OK, 0); } } } static void ssd_routine_start(void *data) { struct ssd_device *dev; if (!data) { return; } dev = data; dev->routine_tick++; if (test_bit(SSD_INIT_WORKQ, &dev->state) && !ssd_busy(dev)) { (void)test_and_set_bit(SSD_LOG_HW, &dev->state); queue_work(dev->workq, &dev->log_work); } if ((dev->routine_tick % SSD_HWMON_ROUTINE_TICK) == 0 && test_bit(SSD_INIT_WORKQ, &dev->state)) { queue_work(dev->workq, &dev->hwmon_work); } if ((dev->routine_tick % SSD_CAPMON_ROUTINE_TICK) == 0 && test_bit(SSD_INIT_WORKQ, &dev->state)) { queue_work(dev->workq, &dev->capmon_work); } if ((dev->routine_tick % SSD_CAPMON2_ROUTINE_TICK) == 0 && test_bit(SSD_HWMON_PL_CAP(SSD_PL_CAP), &dev->hwmon) && test_bit(SSD_INIT_WORKQ, &dev->state)) { /* CAP fault? check again */ queue_work(dev->workq, &dev->capmon_work); } if (test_bit(SSD_INIT_WORKQ, &dev->state)) { queue_work(dev->workq, &dev->tempmon_work); } /* schedule routine */ mod_timer(&dev->routine_timer, jiffies + msecs_to_jiffies(SSD_ROUTINE_INTERVAL)); } static void ssd_cleanup_routine(struct ssd_device *dev) { if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return; (void)ssd_del_timer(&dev->routine_timer); (void)ssd_del_timer(&dev->bm_timer); } static int ssd_init_routine(struct ssd_device *dev) { if (unlikely(mode != SSD_DRV_MODE_STANDARD)) return 0; #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) INIT_WORK(&dev->bm_work, ssd_bm_worker, dev); INIT_WORK(&dev->hwmon_work, ssd_hwmon_worker, dev); INIT_WORK(&dev->capmon_work, ssd_capmon_worker, dev); INIT_WORK(&dev->tempmon_work, ssd_tempmon_worker, dev); #else INIT_WORK(&dev->bm_work, ssd_bm_worker); INIT_WORK(&dev->hwmon_work, ssd_hwmon_worker); INIT_WORK(&dev->capmon_work, ssd_capmon_worker); INIT_WORK(&dev->tempmon_work, ssd_tempmon_worker); #endif /* initial log */ ssd_initial_log(dev); /* schedule bm routine */ ssd_add_timer(&dev->bm_timer, msecs_to_jiffies(SSD_BM_CAP_LEARNING_DELAY), ssd_bm_routine_start, dev); /* schedule routine */ ssd_add_timer(&dev->routine_timer, msecs_to_jiffies(SSD_ROUTINE_INTERVAL), ssd_routine_start, dev); return 0; } static void #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)) __devexit #endif ssd_remove_one (struct pci_dev *pdev) { struct ssd_device *dev; if (!pdev) { return; } dev = pci_get_drvdata(pdev); if (!dev) { return; } list_del_init(&dev->list); ssd_unregister_sysfs(dev); /* offline firstly */ test_and_clear_bit(SSD_ONLINE, &dev->state); /* clean work queue first */ if (!dev->slave) { test_and_clear_bit(SSD_INIT_WORKQ, &dev->state); ssd_cleanup_workq(dev); } /* flush cache */ (void)ssd_flush(dev); (void)ssd_save_md(dev); /* save smart */ if (!dev->slave) { ssd_save_smart(dev); } if (test_and_clear_bit(SSD_INIT_BD, &dev->state)) { ssd_cleanup_blkdev(dev); } if (!dev->slave) { ssd_cleanup_chardev(dev); } /* clean routine */ if (!dev->slave) { ssd_cleanup_routine(dev); } ssd_cleanup_queue(dev); ssd_cleanup_tag(dev); ssd_cleanup_thread(dev); ssd_free_irq(dev); ssd_cleanup_dcmd(dev); ssd_cleanup_cmd(dev); ssd_cleanup_response(dev); if (!dev->slave) { ssd_cleanup_log(dev); } if (dev->reload_fw) { //reload fw ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FW); } /* unmap physical adress */ #ifdef LINUX_SUSE_OS iounmap(dev->ctrlp); #else pci_iounmap(pdev, dev->ctrlp); #endif release_mem_region(dev->mmio_base, dev->mmio_len); pci_disable_device(pdev); pci_set_drvdata(pdev, NULL); ssd_put(dev); } static int #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)) __devinit #endif ssd_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct ssd_device *dev; int ret = 0; if (!pdev || !ent) { ret = -EINVAL; goto out; } dev = kmalloc(sizeof(struct ssd_device), GFP_KERNEL); if (!dev) { ret = -ENOMEM; goto out_alloc_dev; } memset(dev, 0, sizeof(struct ssd_device)); dev->owner = THIS_MODULE; if (SSD_SLAVE_PORT_DEVID == ent->device) { dev->slave = 1; } dev->idx = ssd_get_index(dev->slave); if (dev->idx < 0) { ret = -ENOMEM; goto out_get_index; } if (!dev->slave) { snprintf(dev->name, SSD_DEV_NAME_LEN, SSD_DEV_NAME); ssd_set_dev_name(&dev->name[strlen(SSD_DEV_NAME)], SSD_DEV_NAME_LEN-strlen(SSD_DEV_NAME), dev->idx); dev->major = ssd_major; dev->cmajor = ssd_cmajor; } else { snprintf(dev->name, SSD_DEV_NAME_LEN, SSD_SDEV_NAME); ssd_set_dev_name(&dev->name[strlen(SSD_SDEV_NAME)], SSD_DEV_NAME_LEN-strlen(SSD_SDEV_NAME), dev->idx); dev->major = ssd_major_sl; dev->cmajor = 0; } atomic_set(&(dev->refcnt), 0); atomic_set(&(dev->tocnt), 0); mutex_init(&dev->fw_mutex); //xx mutex_init(&dev->gd_mutex); dev->pdev = pdev; pci_set_drvdata(pdev, dev); kref_init(&dev->kref); ret = pci_enable_device(pdev); if (ret) { hio_warn("%s: can not enable device\n", dev->name); goto out_enable_device; } pci_set_master(pdev); #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)) ret = pci_set_dma_mask(pdev, DMA_64BIT_MASK); #else ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64)); #endif if (ret) { hio_warn("%s: set dma mask: failed\n", dev->name); goto out_set_dma_mask; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)) ret = pci_set_consistent_dma_mask(pdev, DMA_64BIT_MASK); #else ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)); #endif if (ret) { hio_warn("%s: set consistent dma mask: failed\n", dev->name); goto out_set_dma_mask; } dev->mmio_base = pci_resource_start(pdev, 0); dev->mmio_len = pci_resource_len(pdev, 0); if (!request_mem_region(dev->mmio_base, dev->mmio_len, SSD_DEV_NAME)) { hio_warn("%s: can not reserve MMIO region 0\n", dev->name); ret = -EBUSY; goto out_request_mem_region; } /* 2.6.9 kernel bug */ dev->ctrlp = pci_iomap(pdev, 0, 0); if (!dev->ctrlp) { hio_warn("%s: can not remap IO region 0\n", dev->name); ret = -ENOMEM; goto out_pci_iomap; } ret = ssd_check_hw(dev); if (ret) { hio_err("%s: check hardware failed\n", dev->name); goto out_check_hw; } ret = ssd_init_protocol_info(dev); if (ret) { hio_err("%s: init protocol info failed\n", dev->name); goto out_init_protocol_info; } /* alarm led ? */ ssd_clear_alarm(dev); ret = ssd_init_fw_info(dev); if (ret) { hio_err("%s: init firmware info failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_fw_info; } /* slave port ? */ if (dev->slave) { goto init_next1; } ret = ssd_init_rom_info(dev); if (ret) { hio_err("%s: init rom info failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_rom_info; } ret = ssd_init_label(dev); if (ret) { hio_err("%s: init label failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_label; } ret = ssd_init_workq(dev); if (ret) { hio_warn("%s: init workq failed\n", dev->name); goto out_init_workq; } (void)test_and_set_bit(SSD_INIT_WORKQ, &dev->state); ret = ssd_init_log(dev); if (ret) { hio_err("%s: init log failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_log; } ret = ssd_init_smart(dev); if (ret) { hio_err("%s: init info failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_smart; } init_next1: ret = ssd_init_hw_info(dev); if (ret) { hio_err("%s: init hardware info failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_hw_info; } /* slave port ? */ if (dev->slave) { goto init_next2; } ret = ssd_init_sensor(dev); if (ret) { hio_err("%s: init sensor failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_sensor; } ret = ssd_init_pl_cap(dev); if (ret) { hio_err("%s: int pl_cap failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_init_pl_cap; } init_next2: ret = ssd_check_init_state(dev); if (ret) { hio_err("%s: check init state failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_check_init_state; } ret = ssd_init_response(dev); if (ret) { hio_warn("%s: init resp_msg failed\n", dev->name); goto out_init_response; } ret = ssd_init_cmd(dev); if (ret) { hio_warn("%s: init msg failed\n", dev->name); goto out_init_cmd; } ret = ssd_init_dcmd(dev); if (ret) { hio_warn("%s: init cmd failed\n", dev->name); goto out_init_dcmd; } ret = ssd_init_irq(dev); if (ret) { hio_warn("%s: init irq failed\n", dev->name); goto out_init_irq; } ret = ssd_init_thread(dev); if (ret) { hio_warn("%s: init thread failed\n", dev->name); goto out_init_thread; } ret = ssd_init_tag(dev); if(ret) { hio_warn("%s: init tags failed\n", dev->name); goto out_init_tags; } /* */ (void)test_and_set_bit(SSD_ONLINE, &dev->state); ret = ssd_init_queue(dev); if (ret) { hio_warn("%s: init queue failed\n", dev->name); goto out_init_queue; } /* slave port ? */ if (dev->slave) { goto init_next3; } ret = ssd_init_ot_protect(dev); if (ret) { hio_err("%s: int ot_protect failed\n", dev->name); /* alarm led */ ssd_set_alarm(dev); goto out_int_ot_protect; } ret = ssd_init_wmode(dev); if (ret) { hio_warn("%s: init write mode\n", dev->name); goto out_init_wmode; } /* init routine after hw is ready */ ret = ssd_init_routine(dev); if (ret) { hio_warn("%s: init routine\n", dev->name); goto out_init_routine; } ret = ssd_init_chardev(dev); if (ret) { hio_warn("%s: register char device failed\n", dev->name); goto out_init_chardev; } init_next3: ret = ssd_init_blkdev(dev); if (ret) { hio_warn("%s: register block device failed\n", dev->name); goto out_init_blkdev; } (void)test_and_set_bit(SSD_INIT_BD, &dev->state); ret = ssd_register_sysfs(dev); if (ret) { hio_warn("%s: register sysfs failed\n", dev->name); goto out_register_sysfs; } dev->save_md = 1; list_add_tail(&dev->list, &ssd_list); return 0; out_register_sysfs: test_and_clear_bit(SSD_INIT_BD, &dev->state); ssd_cleanup_blkdev(dev); out_init_blkdev: /* slave port ? */ if (!dev->slave) { ssd_cleanup_chardev(dev); } out_init_chardev: /* slave port ? */ if (!dev->slave) { ssd_cleanup_routine(dev); } out_init_routine: out_init_wmode: out_int_ot_protect: ssd_cleanup_queue(dev); out_init_queue: test_and_clear_bit(SSD_ONLINE, &dev->state); ssd_cleanup_tag(dev); out_init_tags: ssd_cleanup_thread(dev); out_init_thread: ssd_free_irq(dev); out_init_irq: ssd_cleanup_dcmd(dev); out_init_dcmd: ssd_cleanup_cmd(dev); out_init_cmd: ssd_cleanup_response(dev); out_init_response: out_check_init_state: out_init_pl_cap: out_init_sensor: out_init_hw_info: out_init_smart: /* slave port ? */ if (!dev->slave) { ssd_cleanup_log(dev); } out_init_log: /* slave port ? */ if (!dev->slave) { test_and_clear_bit(SSD_INIT_WORKQ, &dev->state); ssd_cleanup_workq(dev); } out_init_workq: out_init_label: out_init_rom_info: out_init_fw_info: out_init_protocol_info: out_check_hw: #ifdef LINUX_SUSE_OS iounmap(dev->ctrlp); #else pci_iounmap(pdev, dev->ctrlp); #endif out_pci_iomap: release_mem_region(dev->mmio_base, dev->mmio_len); out_request_mem_region: out_set_dma_mask: pci_disable_device(pdev); out_enable_device: pci_set_drvdata(pdev, NULL); out_get_index: kfree(dev); out_alloc_dev: out: return ret; } static void ssd_cleanup_tasklet(void) { int i; for_each_online_cpu(i) { tasklet_kill(&per_cpu(ssd_tasklet, i)); } } static int ssd_init_tasklet(void) { int i; for_each_online_cpu(i) { INIT_LIST_HEAD(&per_cpu(ssd_doneq, i)); if (finject) { tasklet_init(&per_cpu(ssd_tasklet, i), __ssd_done_db, 0); } else { tasklet_init(&per_cpu(ssd_tasklet, i), __ssd_done, 0); } } return 0; } static struct pci_device_id ssd_pci_tbl[] = { { 0x10ee, 0x0007, PCI_ANY_ID, PCI_ANY_ID, }, /* g3 */ { 0x19e5, 0x0007, PCI_ANY_ID, PCI_ANY_ID, }, /* v1 */ //{ 0x19e5, 0x0008, PCI_ANY_ID, PCI_ANY_ID, }, /* v1 sp*/ { 0x19e5, 0x0009, PCI_ANY_ID, PCI_ANY_ID, }, /* v2 */ { 0x19e5, 0x000a, PCI_ANY_ID, PCI_ANY_ID, }, /* v2 dp slave*/ { 0, } }; MODULE_DEVICE_TABLE(pci, ssd_pci_tbl); static struct pci_driver ssd_driver = { .name = MODULE_NAME, .id_table = ssd_pci_tbl, .probe = ssd_init_one, #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)) .remove = __devexit_p(ssd_remove_one), #else .remove = ssd_remove_one, #endif }; /* notifier block to get a notify on system shutdown/halt/reboot */ static int ssd_notify_reboot(struct notifier_block *nb, unsigned long event, void *buf) { struct ssd_device *dev = NULL; struct ssd_device *n = NULL; list_for_each_entry_safe(dev, n, &ssd_list, list) { ssd_gen_swlog(dev, SSD_LOG_POWER_OFF, 0); (void)ssd_flush(dev); (void)ssd_save_md(dev); /* slave port ? */ if (!dev->slave) { ssd_save_smart(dev); ssd_stop_workq(dev); if (dev->reload_fw) { ssd_reg32_write(dev->ctrlp + SSD_RELOAD_FW_REG, SSD_RELOAD_FW); } } } return NOTIFY_OK; } static struct notifier_block ssd_notifier = { ssd_notify_reboot, NULL, 0 }; static int __init ssd_init_module(void) { int ret = 0; hio_info("driver version: %s\n", DRIVER_VERSION); ret = ssd_init_index(); if (ret) { hio_warn("init index failed\n"); goto out_init_index; } ret = ssd_init_proc(); if (ret) { hio_warn("init proc failed\n"); goto out_init_proc; } ret = ssd_init_sysfs(); if (ret) { hio_warn("init sysfs failed\n"); goto out_init_sysfs; } ret = ssd_init_tasklet(); if (ret) { hio_warn("init tasklet failed\n"); goto out_init_tasklet; } #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)) ssd_class = class_simple_create(THIS_MODULE, SSD_DEV_NAME); #else ssd_class = class_create(THIS_MODULE, SSD_DEV_NAME); #endif if (IS_ERR(ssd_class)) { ret = PTR_ERR(ssd_class); goto out_class_create; } if (ssd_cmajor > 0) { ret = register_chrdev(ssd_cmajor, SSD_CDEV_NAME, &ssd_cfops); } else { ret = ssd_cmajor = register_chrdev(ssd_cmajor, SSD_CDEV_NAME, &ssd_cfops); } if (ret < 0) { hio_warn("unable to register chardev major number\n"); goto out_register_chardev; } if (ssd_major > 0) { ret = register_blkdev(ssd_major, SSD_DEV_NAME); } else { ret = ssd_major = register_blkdev(ssd_major, SSD_DEV_NAME); } if (ret < 0) { hio_warn("unable to register major number\n"); goto out_register_blkdev; } if (ssd_major_sl > 0) { ret = register_blkdev(ssd_major_sl, SSD_SDEV_NAME); } else { ret = ssd_major_sl = register_blkdev(ssd_major_sl, SSD_SDEV_NAME); } if (ret < 0) { hio_warn("unable to register slave major number\n"); goto out_register_blkdev_sl; } if (mode < SSD_DRV_MODE_STANDARD || mode > SSD_DRV_MODE_BASE) { mode = SSD_DRV_MODE_STANDARD; } /* for debug */ if (mode != SSD_DRV_MODE_STANDARD) { ssd_minors = 1; } if (int_mode < SSD_INT_LEGACY || int_mode > SSD_INT_MSIX) { int_mode = SSD_INT_MODE_DEFAULT; } if (threaded_irq) { int_mode = SSD_INT_MSI; } if (log_level >= SSD_LOG_NR_LEVEL || log_level < SSD_LOG_LEVEL_INFO) { log_level = SSD_LOG_LEVEL_ERR; } if (wmode < SSD_WMODE_BUFFER || wmode > SSD_WMODE_DEFAULT) { wmode = SSD_WMODE_DEFAULT; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)) ret = pci_module_init(&ssd_driver); #else ret = pci_register_driver(&ssd_driver); #endif if (ret) { hio_warn("pci init failed\n"); goto out_pci_init; } ret = register_reboot_notifier(&ssd_notifier); if (ret) { hio_warn("register reboot notifier failed\n"); goto out_register_reboot_notifier; } return 0; out_register_reboot_notifier: out_pci_init: pci_unregister_driver(&ssd_driver); unregister_blkdev(ssd_major_sl, SSD_SDEV_NAME); out_register_blkdev_sl: unregister_blkdev(ssd_major, SSD_DEV_NAME); out_register_blkdev: unregister_chrdev(ssd_cmajor, SSD_CDEV_NAME); out_register_chardev: #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)) class_simple_destroy(ssd_class); #else class_destroy(ssd_class); #endif out_class_create: ssd_cleanup_tasklet(); out_init_tasklet: ssd_cleanup_sysfs(); out_init_sysfs: ssd_cleanup_proc(); out_init_proc: ssd_cleanup_index(); out_init_index: return ret; } static void __exit ssd_cleanup_module(void) { hio_info("unload driver: %s\n", DRIVER_VERSION); /* exiting */ ssd_exiting = 1; unregister_reboot_notifier(&ssd_notifier); pci_unregister_driver(&ssd_driver); unregister_blkdev(ssd_major_sl, SSD_SDEV_NAME); unregister_blkdev(ssd_major, SSD_DEV_NAME); unregister_chrdev(ssd_cmajor, SSD_CDEV_NAME); #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)) class_simple_destroy(ssd_class); #else class_destroy(ssd_class); #endif ssd_cleanup_tasklet(); ssd_cleanup_sysfs(); ssd_cleanup_proc(); ssd_cleanup_index(); } int ssd_register_event_notifier(struct block_device *bdev, ssd_event_call event_call) { struct ssd_device *dev; struct timeval tv; struct ssd_log *le; uint64_t cur; int log_nr; if (!bdev || !event_call || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; dev->event_call = event_call; do_gettimeofday(&tv); cur = tv.tv_sec; le = (struct ssd_log *)(dev->internal_log.log); log_nr = dev->internal_log.nr_log; while (log_nr--) { if (le->time <= cur && le->time >= dev->uptime) { (void)dev->event_call(dev->gd, le->le.event, ssd_parse_log(dev, le, 0)); } le++; } return 0; } int ssd_unregister_event_notifier(struct block_device *bdev) { struct ssd_device *dev; if (!bdev || !(bdev->bd_disk)) { return -EINVAL; } dev = bdev->bd_disk->private_data; dev->event_call = NULL; return 0; } EXPORT_SYMBOL(ssd_get_label); EXPORT_SYMBOL(ssd_get_version); EXPORT_SYMBOL(ssd_set_otprotect); EXPORT_SYMBOL(ssd_bm_status); EXPORT_SYMBOL(ssd_submit_pbio); EXPORT_SYMBOL(ssd_get_pciaddr); EXPORT_SYMBOL(ssd_get_temperature); EXPORT_SYMBOL(ssd_register_event_notifier); EXPORT_SYMBOL(ssd_unregister_event_notifier); EXPORT_SYMBOL(ssd_reset); EXPORT_SYMBOL(ssd_set_wmode); module_init(ssd_init_module); module_exit(ssd_cleanup_module); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Huawei SSD DEV Team"); MODULE_DESCRIPTION("Huawei SSD driver");