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 /drivers/input/touchscreen | |
| 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>
Diffstat (limited to 'drivers/input/touchscreen')
| -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; |
