diff options
author | Nick Dyer <nick.dyer@itdev.co.uk> | 2014-07-23 15:40:09 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2014-07-23 17:42:08 -0400 |
commit | 4ce6fa017f48e892cc3465caa7fbb3dead5a6ca6 (patch) | |
tree | df6ee6662152f8adc047ab675c61f042140ee629 | |
parent | 50a77c658b80e7e3303e3bcec195b30e2b62d513 (diff) |
Input: atmel_mxt_ts - calculate and check CRC in config file
By validating the checksum, we can identify if the configuration is
corrupt. In addition, this patch writes the configuration in a short
series of block writes rather than as many individual values.
Signed-off-by: Nick Dyer <nick.dyer@itdev.co.uk>
Acked-by: Benson Leung <bleung@chromium.org>
Acked-by: Yufeng Shen <miletus@chromium.org>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
-rw-r--r-- | drivers/input/touchscreen/atmel_mxt_ts.c | 232 |
1 files changed, 177 insertions, 55 deletions
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index c6b5e7dbd942..d2feb9c771a4 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c | |||
@@ -48,6 +48,8 @@ | |||
48 | #define MXT_OBJECT_START 0x07 | 48 | #define MXT_OBJECT_START 0x07 |
49 | 49 | ||
50 | #define MXT_OBJECT_SIZE 6 | 50 | #define MXT_OBJECT_SIZE 6 |
51 | #define MXT_INFO_CHECKSUM_SIZE 3 | ||
52 | #define MXT_MAX_BLOCK_WRITE 256 | ||
51 | 53 | ||
52 | /* Object types */ | 54 | /* Object types */ |
53 | #define MXT_DEBUG_DIAGNOSTIC_T37 37 | 55 | #define MXT_DEBUG_DIAGNOSTIC_T37 37 |
@@ -238,12 +240,15 @@ struct mxt_data { | |||
238 | unsigned int max_x; | 240 | unsigned int max_x; |
239 | unsigned int max_y; | 241 | unsigned int max_y; |
240 | bool in_bootloader; | 242 | bool in_bootloader; |
243 | u16 mem_size; | ||
241 | u32 config_crc; | 244 | u32 config_crc; |
245 | u32 info_crc; | ||
242 | u8 bootloader_addr; | 246 | u8 bootloader_addr; |
243 | 247 | ||
244 | /* Cached parameters from object table */ | 248 | /* Cached parameters from object table */ |
245 | u8 T6_reportid; | 249 | u8 T6_reportid; |
246 | u16 T6_address; | 250 | u16 T6_address; |
251 | u16 T7_address; | ||
247 | u8 T9_reportid_min; | 252 | u8 T9_reportid_min; |
248 | u8 T9_reportid_max; | 253 | u8 T9_reportid_max; |
249 | u8 T19_reportid; | 254 | u8 T19_reportid; |
@@ -849,6 +854,45 @@ static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value) | |||
849 | mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT); | 854 | mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT); |
850 | } | 855 | } |
851 | 856 | ||
857 | static void mxt_calc_crc24(u32 *crc, u8 firstbyte, u8 secondbyte) | ||
858 | { | ||
859 | static const unsigned int crcpoly = 0x80001B; | ||
860 | u32 result; | ||
861 | u32 data_word; | ||
862 | |||
863 | data_word = (secondbyte << 8) | firstbyte; | ||
864 | result = ((*crc << 1) ^ data_word); | ||
865 | |||
866 | if (result & 0x1000000) | ||
867 | result ^= crcpoly; | ||
868 | |||
869 | *crc = result; | ||
870 | } | ||
871 | |||
872 | static u32 mxt_calculate_crc(u8 *base, off_t start_off, off_t end_off) | ||
873 | { | ||
874 | u32 crc = 0; | ||
875 | u8 *ptr = base + start_off; | ||
876 | u8 *last_val = base + end_off - 1; | ||
877 | |||
878 | if (end_off < start_off) | ||
879 | return -EINVAL; | ||
880 | |||
881 | while (ptr < last_val) { | ||
882 | mxt_calc_crc24(&crc, *ptr, *(ptr + 1)); | ||
883 | ptr += 2; | ||
884 | } | ||
885 | |||
886 | /* if len is odd, fill the last byte with 0 */ | ||
887 | if (ptr == last_val) | ||
888 | mxt_calc_crc24(&crc, *ptr, 0); | ||
889 | |||
890 | /* Mask to 24-bit */ | ||
891 | crc &= 0x00FFFFFF; | ||
892 | |||
893 | return crc; | ||
894 | } | ||
895 | |||
852 | /* | 896 | /* |
853 | * mxt_update_cfg - download configuration to chip | 897 | * mxt_update_cfg - download configuration to chip |
854 | * | 898 | * |
@@ -875,9 +919,13 @@ static int mxt_update_cfg(struct mxt_data *data, const struct firmware *cfg) | |||
875 | struct mxt_object *object; | 919 | struct mxt_object *object; |
876 | int ret; | 920 | int ret; |
877 | int offset; | 921 | int offset; |
878 | int pos; | 922 | int data_pos; |
923 | int byte_offset; | ||
879 | int i; | 924 | int i; |
880 | u32 info_crc, config_crc; | 925 | int cfg_start_ofs; |
926 | u32 info_crc, config_crc, calculated_crc; | ||
927 | u8 *config_mem; | ||
928 | size_t config_mem_size; | ||
881 | unsigned int type, instance, size; | 929 | unsigned int type, instance, size; |
882 | u8 val; | 930 | u8 val; |
883 | u16 reg; | 931 | u16 reg; |
@@ -890,11 +938,11 @@ static int mxt_update_cfg(struct mxt_data *data, const struct firmware *cfg) | |||
890 | goto release; | 938 | goto release; |
891 | } | 939 | } |
892 | 940 | ||
893 | pos = strlen(MXT_CFG_MAGIC); | 941 | data_pos = strlen(MXT_CFG_MAGIC); |
894 | 942 | ||
895 | /* Load information block and check */ | 943 | /* Load information block and check */ |
896 | for (i = 0; i < sizeof(struct mxt_info); i++) { | 944 | for (i = 0; i < sizeof(struct mxt_info); i++) { |
897 | ret = sscanf(cfg->data + pos, "%hhx%n", | 945 | ret = sscanf(cfg->data + data_pos, "%hhx%n", |
898 | (unsigned char *)&cfg_info + i, | 946 | (unsigned char *)&cfg_info + i, |
899 | &offset); | 947 | &offset); |
900 | if (ret != 1) { | 948 | if (ret != 1) { |
@@ -903,7 +951,7 @@ static int mxt_update_cfg(struct mxt_data *data, const struct firmware *cfg) | |||
903 | goto release; | 951 | goto release; |
904 | } | 952 | } |
905 | 953 | ||
906 | pos += offset; | 954 | data_pos += offset; |
907 | } | 955 | } |
908 | 956 | ||
909 | if (cfg_info.family_id != data->info.family_id) { | 957 | if (cfg_info.family_id != data->info.family_id) { |
@@ -918,125 +966,188 @@ static int mxt_update_cfg(struct mxt_data *data, const struct firmware *cfg) | |||
918 | goto release; | 966 | goto release; |
919 | } | 967 | } |
920 | 968 | ||
921 | if (cfg_info.version != data->info.version) | 969 | /* Read CRCs */ |
922 | dev_err(dev, "Warning: version mismatch!\n"); | 970 | ret = sscanf(cfg->data + data_pos, "%x%n", &info_crc, &offset); |
923 | |||
924 | if (cfg_info.build != data->info.build) | ||
925 | dev_err(dev, "Warning: build num mismatch!\n"); | ||
926 | |||
927 | ret = sscanf(cfg->data + pos, "%x%n", &info_crc, &offset); | ||
928 | if (ret != 1) { | 971 | if (ret != 1) { |
929 | dev_err(dev, "Bad format: failed to parse Info CRC\n"); | 972 | dev_err(dev, "Bad format: failed to parse Info CRC\n"); |
930 | ret = -EINVAL; | 973 | ret = -EINVAL; |
931 | goto release; | 974 | goto release; |
932 | } | 975 | } |
933 | pos += offset; | 976 | data_pos += offset; |
934 | 977 | ||
935 | /* Check config CRC */ | 978 | ret = sscanf(cfg->data + data_pos, "%x%n", &config_crc, &offset); |
936 | ret = sscanf(cfg->data + pos, "%x%n", &config_crc, &offset); | ||
937 | if (ret != 1) { | 979 | if (ret != 1) { |
938 | dev_err(dev, "Bad format: failed to parse Config CRC\n"); | 980 | dev_err(dev, "Bad format: failed to parse Config CRC\n"); |
939 | ret = -EINVAL; | 981 | ret = -EINVAL; |
940 | goto release; | 982 | goto release; |
941 | } | 983 | } |
942 | pos += offset; | 984 | data_pos += offset; |
943 | 985 | ||
944 | if (data->config_crc == config_crc) { | 986 | /* |
945 | dev_dbg(dev, "Config CRC 0x%06X: OK\n", config_crc); | 987 | * The Info Block CRC is calculated over mxt_info and the object |
946 | ret = 0; | 988 | * table. If it does not match then we are trying to load the |
947 | goto release; | 989 | * configuration from a different chip or firmware version, so |
990 | * the configuration CRC is invalid anyway. | ||
991 | */ | ||
992 | if (info_crc == data->info_crc) { | ||
993 | if (config_crc == 0 || data->config_crc == 0) { | ||
994 | dev_info(dev, "CRC zero, attempting to apply config\n"); | ||
995 | } else if (config_crc == data->config_crc) { | ||
996 | dev_dbg(dev, "Config CRC 0x%06X: OK\n", | ||
997 | data->config_crc); | ||
998 | ret = 0; | ||
999 | goto release; | ||
1000 | } else { | ||
1001 | dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n", | ||
1002 | data->config_crc, config_crc); | ||
1003 | } | ||
1004 | } else { | ||
1005 | dev_warn(dev, | ||
1006 | "Warning: Info CRC error - device=0x%06X file=0x%06X\n", | ||
1007 | data->info_crc, info_crc); | ||
948 | } | 1008 | } |
949 | 1009 | ||
950 | dev_info(dev, "Config CRC 0x%06X: does not match file 0x%06X\n", | 1010 | /* Malloc memory to store configuration */ |
951 | data->config_crc, config_crc); | 1011 | cfg_start_ofs = MXT_OBJECT_START + |
1012 | data->info.object_num * sizeof(struct mxt_object) + | ||
1013 | MXT_INFO_CHECKSUM_SIZE; | ||
1014 | config_mem_size = data->mem_size - cfg_start_ofs; | ||
1015 | config_mem = kzalloc(config_mem_size, GFP_KERNEL); | ||
1016 | if (!config_mem) { | ||
1017 | dev_err(dev, "Failed to allocate memory\n"); | ||
1018 | ret = -ENOMEM; | ||
1019 | goto release; | ||
1020 | } | ||
952 | 1021 | ||
953 | while (pos < cfg->size) { | 1022 | while (data_pos < cfg->size) { |
954 | /* Read type, instance, length */ | 1023 | /* Read type, instance, length */ |
955 | ret = sscanf(cfg->data + pos, "%x %x %x%n", | 1024 | ret = sscanf(cfg->data + data_pos, "%x %x %x%n", |
956 | &type, &instance, &size, &offset); | 1025 | &type, &instance, &size, &offset); |
957 | if (ret == 0) { | 1026 | if (ret == 0) { |
958 | /* EOF */ | 1027 | /* EOF */ |
959 | ret = 1; | 1028 | break; |
960 | goto release; | ||
961 | } else if (ret != 3) { | 1029 | } else if (ret != 3) { |
962 | dev_err(dev, "Bad format: failed to parse object\n"); | 1030 | dev_err(dev, "Bad format: failed to parse object\n"); |
963 | ret = -EINVAL; | 1031 | ret = -EINVAL; |
964 | goto release; | 1032 | goto release_mem; |
965 | } | 1033 | } |
966 | pos += offset; | 1034 | data_pos += offset; |
967 | 1035 | ||
968 | object = mxt_get_object(data, type); | 1036 | object = mxt_get_object(data, type); |
969 | if (!object) { | 1037 | if (!object) { |
970 | /* Skip object */ | 1038 | /* Skip object */ |
971 | for (i = 0; i < size; i++) { | 1039 | for (i = 0; i < size; i++) { |
972 | ret = sscanf(cfg->data + pos, "%hhx%n", | 1040 | ret = sscanf(cfg->data + data_pos, "%hhx%n", |
973 | &val, | 1041 | &val, |
974 | &offset); | 1042 | &offset); |
975 | pos += offset; | 1043 | data_pos += offset; |
976 | } | 1044 | } |
977 | continue; | 1045 | continue; |
978 | } | 1046 | } |
979 | 1047 | ||
980 | if (size > mxt_obj_size(object)) { | 1048 | if (size > mxt_obj_size(object)) { |
981 | dev_err(dev, "Discarding %zu byte(s) in T%u\n", | 1049 | /* |
982 | size - mxt_obj_size(object), type); | 1050 | * Either we are in fallback mode due to wrong |
1051 | * config or config from a later fw version, | ||
1052 | * or the file is corrupt or hand-edited. | ||
1053 | */ | ||
1054 | dev_warn(dev, "Discarding %zu byte(s) in T%u\n", | ||
1055 | size - mxt_obj_size(object), type); | ||
1056 | } else if (mxt_obj_size(object) > size) { | ||
1057 | /* | ||
1058 | * If firmware is upgraded, new bytes may be added to | ||
1059 | * end of objects. It is generally forward compatible | ||
1060 | * to zero these bytes - previous behaviour will be | ||
1061 | * retained. However this does invalidate the CRC and | ||
1062 | * will force fallback mode until the configuration is | ||
1063 | * updated. We warn here but do nothing else - the | ||
1064 | * malloc has zeroed the entire configuration. | ||
1065 | */ | ||
1066 | dev_warn(dev, "Zeroing %zu byte(s) in T%d\n", | ||
1067 | mxt_obj_size(object) - size, type); | ||
983 | } | 1068 | } |
984 | 1069 | ||
985 | if (instance >= mxt_obj_instances(object)) { | 1070 | if (instance >= mxt_obj_instances(object)) { |
986 | dev_err(dev, "Object instances exceeded!\n"); | 1071 | dev_err(dev, "Object instances exceeded!\n"); |
987 | ret = -EINVAL; | 1072 | ret = -EINVAL; |
988 | goto release; | 1073 | goto release_mem; |
989 | } | 1074 | } |
990 | 1075 | ||
991 | reg = object->start_address + mxt_obj_size(object) * instance; | 1076 | reg = object->start_address + mxt_obj_size(object) * instance; |
992 | 1077 | ||
993 | for (i = 0; i < size; i++) { | 1078 | for (i = 0; i < size; i++) { |
994 | ret = sscanf(cfg->data + pos, "%hhx%n", | 1079 | ret = sscanf(cfg->data + data_pos, "%hhx%n", |
995 | &val, | 1080 | &val, |
996 | &offset); | 1081 | &offset); |
997 | if (ret != 1) { | 1082 | if (ret != 1) { |
998 | dev_err(dev, "Bad format in T%d\n", type); | 1083 | dev_err(dev, "Bad format in T%d\n", type); |
999 | ret = -EINVAL; | 1084 | ret = -EINVAL; |
1000 | goto release; | 1085 | goto release_mem; |
1001 | } | 1086 | } |
1002 | pos += offset; | 1087 | data_pos += offset; |
1003 | 1088 | ||
1004 | if (i > mxt_obj_size(object)) | 1089 | if (i > mxt_obj_size(object)) |
1005 | continue; | 1090 | continue; |
1006 | 1091 | ||
1007 | ret = mxt_write_reg(data->client, reg + i, val); | 1092 | byte_offset = reg + i - cfg_start_ofs; |
1008 | if (ret) | ||
1009 | goto release; | ||
1010 | 1093 | ||
1094 | if ((byte_offset >= 0) | ||
1095 | && (byte_offset <= config_mem_size)) { | ||
1096 | *(config_mem + byte_offset) = val; | ||
1097 | } else { | ||
1098 | dev_err(dev, "Bad object: reg:%d, T%d, ofs=%d\n", | ||
1099 | reg, object->type, byte_offset); | ||
1100 | ret = -EINVAL; | ||
1101 | goto release_mem; | ||
1102 | } | ||
1011 | } | 1103 | } |
1104 | } | ||
1012 | 1105 | ||
1013 | /* | 1106 | /* Calculate crc of the received configs (not the raw config file) */ |
1014 | * If firmware is upgraded, new bytes may be added to end of | 1107 | if (data->T7_address < cfg_start_ofs) { |
1015 | * objects. It is generally forward compatible to zero these | 1108 | dev_err(dev, "Bad T7 address, T7addr = %x, config offset %x\n", |
1016 | * bytes - previous behaviour will be retained. However | 1109 | data->T7_address, cfg_start_ofs); |
1017 | * this does invalidate the CRC and will force a config | 1110 | ret = 0; |
1018 | * download every time until the configuration is updated. | 1111 | goto release_mem; |
1019 | */ | 1112 | } |
1020 | if (size < mxt_obj_size(object)) { | ||
1021 | dev_info(dev, "Zeroing %zu byte(s) in T%d\n", | ||
1022 | mxt_obj_size(object) - size, type); | ||
1023 | 1113 | ||
1024 | for (i = size + 1; i < mxt_obj_size(object); i++) { | 1114 | calculated_crc = mxt_calculate_crc(config_mem, |
1025 | ret = mxt_write_reg(data->client, reg + i, 0); | 1115 | data->T7_address - cfg_start_ofs, |
1026 | if (ret) | 1116 | config_mem_size); |
1027 | goto release; | 1117 | |
1028 | } | 1118 | if (config_crc > 0 && (config_crc != calculated_crc)) |
1119 | dev_warn(dev, "Config CRC error, calculated=%06X, file=%06X\n", | ||
1120 | calculated_crc, config_crc); | ||
1121 | |||
1122 | /* Write configuration as blocks */ | ||
1123 | byte_offset = 0; | ||
1124 | while (byte_offset < config_mem_size) { | ||
1125 | size = config_mem_size - byte_offset; | ||
1126 | |||
1127 | if (size > MXT_MAX_BLOCK_WRITE) | ||
1128 | size = MXT_MAX_BLOCK_WRITE; | ||
1129 | |||
1130 | ret = __mxt_write_reg(data->client, | ||
1131 | cfg_start_ofs + byte_offset, | ||
1132 | size, config_mem + byte_offset); | ||
1133 | if (ret != 0) { | ||
1134 | dev_err(dev, "Config write error, ret=%d\n", ret); | ||
1135 | goto release_mem; | ||
1029 | } | 1136 | } |
1137 | |||
1138 | byte_offset += size; | ||
1030 | } | 1139 | } |
1031 | 1140 | ||
1032 | mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); | 1141 | mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); |
1033 | 1142 | ||
1034 | ret = mxt_soft_reset(data); | 1143 | ret = mxt_soft_reset(data); |
1035 | if (ret) | 1144 | if (ret) |
1036 | goto release; | 1145 | goto release_mem; |
1037 | 1146 | ||
1038 | dev_info(dev, "Config successfully updated\n"); | 1147 | dev_info(dev, "Config successfully updated\n"); |
1039 | 1148 | ||
1149 | release_mem: | ||
1150 | kfree(config_mem); | ||
1040 | release: | 1151 | release: |
1041 | release_firmware(cfg); | 1152 | release_firmware(cfg); |
1042 | return ret; | 1153 | return ret; |
@@ -1099,6 +1210,7 @@ static int mxt_get_object_table(struct mxt_data *data) | |||
1099 | int error; | 1210 | int error; |
1100 | int i; | 1211 | int i; |
1101 | u8 reportid; | 1212 | u8 reportid; |
1213 | u16 end_address; | ||
1102 | 1214 | ||
1103 | table_size = data->info.object_num * sizeof(struct mxt_object); | 1215 | table_size = data->info.object_num * sizeof(struct mxt_object); |
1104 | object_table = kzalloc(table_size, GFP_KERNEL); | 1216 | object_table = kzalloc(table_size, GFP_KERNEL); |
@@ -1116,6 +1228,7 @@ static int mxt_get_object_table(struct mxt_data *data) | |||
1116 | 1228 | ||
1117 | /* Valid Report IDs start counting from 1 */ | 1229 | /* Valid Report IDs start counting from 1 */ |
1118 | reportid = 1; | 1230 | reportid = 1; |
1231 | data->mem_size = 0; | ||
1119 | for (i = 0; i < data->info.object_num; i++) { | 1232 | for (i = 0; i < data->info.object_num; i++) { |
1120 | struct mxt_object *object = object_table + i; | 1233 | struct mxt_object *object = object_table + i; |
1121 | u8 min_id, max_id; | 1234 | u8 min_id, max_id; |
@@ -1143,6 +1256,9 @@ static int mxt_get_object_table(struct mxt_data *data) | |||
1143 | data->T6_reportid = min_id; | 1256 | data->T6_reportid = min_id; |
1144 | data->T6_address = object->start_address; | 1257 | data->T6_address = object->start_address; |
1145 | break; | 1258 | break; |
1259 | case MXT_GEN_POWER_T7: | ||
1260 | data->T7_address = object->start_address; | ||
1261 | break; | ||
1146 | case MXT_TOUCH_MULTI_T9: | 1262 | case MXT_TOUCH_MULTI_T9: |
1147 | data->T9_reportid_min = min_id; | 1263 | data->T9_reportid_min = min_id; |
1148 | data->T9_reportid_max = max_id; | 1264 | data->T9_reportid_max = max_id; |
@@ -1151,6 +1267,12 @@ static int mxt_get_object_table(struct mxt_data *data) | |||
1151 | data->T19_reportid = min_id; | 1267 | data->T19_reportid = min_id; |
1152 | break; | 1268 | break; |
1153 | } | 1269 | } |
1270 | |||
1271 | end_address = object->start_address | ||
1272 | + mxt_obj_size(object) * mxt_obj_instances(object) - 1; | ||
1273 | |||
1274 | if (end_address >= data->mem_size) | ||
1275 | data->mem_size = end_address + 1; | ||
1154 | } | 1276 | } |
1155 | 1277 | ||
1156 | data->object_table = object_table; | 1278 | data->object_table = object_table; |