diff options
author | Xinming Hu <huxm@marvell.com> | 2014-11-24 05:40:53 -0500 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2014-12-03 11:35:51 -0500 |
commit | dc759613b0247eb1658d3992f50ba3fad5b61d31 (patch) | |
tree | 5ae91a8d93f48204c9c3658b2eba00594c2fb689 /drivers/bluetooth/btmrvl_sdio.c | |
parent | 7365d475bf6a0e2497ac4ef29474fffb91d024f1 (diff) |
Bluetooth: btmrvl add firmware dump support
This patch adds firmware dump support for marvell
bluetooth chipset. Currently only SD8897 is supported.
This is implemented based on dev_coredump, a new mechnism
introduced in kernel 3.18rc3
Firmware dump can be trigger by
echo 1 > /sys/kernel/debug/bluetooth/hci*/config/fw_dump
and when the dump operation is completed, data can be read by
cat /sys/class/devcoredump/devcd*/data
We have prepared following script to divide fw memory
dump data into multiple files based on memory type.
[root]# cat btmrvl_split_dump_data.sh
#!/bin/bash
# usage: ./btmrvl_split_dump_data.sh dump_data
fw_dump_data=$1
mem_type="ITCM DTCM SQRAM APU CIU ICU MAC EXT7 EXT8 EXT9 EXT10 EXT11 EXT12 EXT13 EXTLAST"
for name in ${mem_type[@]}
do
sed -n "/Start dump $name/,/End dump/p" $fw_dump_data > tmp.$name.log
if [ ! -s tmp.$name.log ]
then
rm -rf tmp.$name.log
else
# Remove the describle info "Start dump" and "End dump"
sed '1d' tmp.$name.log | sed '$d' > /data/$name.log
if [ -s /data/$name.log ]
then
echo "generate /data/$name.log"
else
sed '1d' tmp.$name.log | sed '$d' > /var/$name.log
echo "generate /var/$name.log"
fi
rm -rf tmp.$name.log
fi
done
Signed-off-by: Xinming Hu <huxm@marvell.com>
Signed-off-by: Cathy Luo <cluo@marvell.com>
Signed-off-by: Avinash Patil <patila@marvell.com>
Reviewed-by: Johannes Berg <johannes@sipsolutions.net>
Reviewed-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: Amitkumar Karwar <akarwar@marvell.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Diffstat (limited to 'drivers/bluetooth/btmrvl_sdio.c')
-rw-r--r-- | drivers/bluetooth/btmrvl_sdio.c | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c index 416d792176c2..0057c0b7a776 100644 --- a/drivers/bluetooth/btmrvl_sdio.c +++ b/drivers/bluetooth/btmrvl_sdio.c | |||
@@ -24,6 +24,7 @@ | |||
24 | #include <linux/mmc/sdio_ids.h> | 24 | #include <linux/mmc/sdio_ids.h> |
25 | #include <linux/mmc/sdio_func.h> | 25 | #include <linux/mmc/sdio_func.h> |
26 | #include <linux/module.h> | 26 | #include <linux/module.h> |
27 | #include <linux/devcoredump.h> | ||
27 | 28 | ||
28 | #include <net/bluetooth/bluetooth.h> | 29 | #include <net/bluetooth/bluetooth.h> |
29 | #include <net/bluetooth/hci_core.h> | 30 | #include <net/bluetooth/hci_core.h> |
@@ -33,6 +34,24 @@ | |||
33 | 34 | ||
34 | #define VERSION "1.0" | 35 | #define VERSION "1.0" |
35 | 36 | ||
37 | static struct memory_type_mapping mem_type_mapping_tbl[] = { | ||
38 | {"ITCM", NULL, 0, 0xF0}, | ||
39 | {"DTCM", NULL, 0, 0xF1}, | ||
40 | {"SQRAM", NULL, 0, 0xF2}, | ||
41 | {"APU", NULL, 0, 0xF3}, | ||
42 | {"CIU", NULL, 0, 0xF4}, | ||
43 | {"ICU", NULL, 0, 0xF5}, | ||
44 | {"MAC", NULL, 0, 0xF6}, | ||
45 | {"EXT7", NULL, 0, 0xF7}, | ||
46 | {"EXT8", NULL, 0, 0xF8}, | ||
47 | {"EXT9", NULL, 0, 0xF9}, | ||
48 | {"EXT10", NULL, 0, 0xFA}, | ||
49 | {"EXT11", NULL, 0, 0xFB}, | ||
50 | {"EXT12", NULL, 0, 0xFC}, | ||
51 | {"EXT13", NULL, 0, 0xFD}, | ||
52 | {"EXTLAST", NULL, 0, 0xFE}, | ||
53 | }; | ||
54 | |||
36 | /* The btmrvl_sdio_remove() callback function is called | 55 | /* The btmrvl_sdio_remove() callback function is called |
37 | * when user removes this module from kernel space or ejects | 56 | * when user removes this module from kernel space or ejects |
38 | * the card from the slot. The driver handles these 2 cases | 57 | * the card from the slot. The driver handles these 2 cases |
@@ -122,6 +141,9 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_8897 = { | |||
122 | .int_read_to_clear = true, | 141 | .int_read_to_clear = true, |
123 | .host_int_rsr = 0x01, | 142 | .host_int_rsr = 0x01, |
124 | .card_misc_cfg = 0xcc, | 143 | .card_misc_cfg = 0xcc, |
144 | .fw_dump_ctrl = 0xe2, | ||
145 | .fw_dump_start = 0xe3, | ||
146 | .fw_dump_end = 0xea, | ||
125 | }; | 147 | }; |
126 | 148 | ||
127 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = { | 149 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = { |
@@ -130,6 +152,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = { | |||
130 | .reg = &btmrvl_reg_8688, | 152 | .reg = &btmrvl_reg_8688, |
131 | .support_pscan_win_report = false, | 153 | .support_pscan_win_report = false, |
132 | .sd_blksz_fw_dl = 64, | 154 | .sd_blksz_fw_dl = 64, |
155 | .supports_fw_dump = false, | ||
133 | }; | 156 | }; |
134 | 157 | ||
135 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = { | 158 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = { |
@@ -138,6 +161,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = { | |||
138 | .reg = &btmrvl_reg_87xx, | 161 | .reg = &btmrvl_reg_87xx, |
139 | .support_pscan_win_report = false, | 162 | .support_pscan_win_report = false, |
140 | .sd_blksz_fw_dl = 256, | 163 | .sd_blksz_fw_dl = 256, |
164 | .supports_fw_dump = false, | ||
141 | }; | 165 | }; |
142 | 166 | ||
143 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = { | 167 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = { |
@@ -146,6 +170,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = { | |||
146 | .reg = &btmrvl_reg_87xx, | 170 | .reg = &btmrvl_reg_87xx, |
147 | .support_pscan_win_report = false, | 171 | .support_pscan_win_report = false, |
148 | .sd_blksz_fw_dl = 256, | 172 | .sd_blksz_fw_dl = 256, |
173 | .supports_fw_dump = false, | ||
149 | }; | 174 | }; |
150 | 175 | ||
151 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8887 = { | 176 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8887 = { |
@@ -154,6 +179,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8887 = { | |||
154 | .reg = &btmrvl_reg_8887, | 179 | .reg = &btmrvl_reg_8887, |
155 | .support_pscan_win_report = true, | 180 | .support_pscan_win_report = true, |
156 | .sd_blksz_fw_dl = 256, | 181 | .sd_blksz_fw_dl = 256, |
182 | .supports_fw_dump = false, | ||
157 | }; | 183 | }; |
158 | 184 | ||
159 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8897 = { | 185 | static const struct btmrvl_sdio_device btmrvl_sdio_sd8897 = { |
@@ -162,6 +188,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8897 = { | |||
162 | .reg = &btmrvl_reg_8897, | 188 | .reg = &btmrvl_reg_8897, |
163 | .support_pscan_win_report = true, | 189 | .support_pscan_win_report = true, |
164 | .sd_blksz_fw_dl = 256, | 190 | .sd_blksz_fw_dl = 256, |
191 | .supports_fw_dump = true, | ||
165 | }; | 192 | }; |
166 | 193 | ||
167 | static const struct sdio_device_id btmrvl_sdio_ids[] = { | 194 | static const struct sdio_device_id btmrvl_sdio_ids[] = { |
@@ -1080,6 +1107,277 @@ static int btmrvl_sdio_wakeup_fw(struct btmrvl_private *priv) | |||
1080 | return ret; | 1107 | return ret; |
1081 | } | 1108 | } |
1082 | 1109 | ||
1110 | static void btmrvl_sdio_dump_regs(struct btmrvl_private *priv) | ||
1111 | { | ||
1112 | struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; | ||
1113 | int ret = 0; | ||
1114 | unsigned int reg, reg_start, reg_end; | ||
1115 | char buf[256], *ptr; | ||
1116 | u8 loop, func, data; | ||
1117 | int MAX_LOOP = 2; | ||
1118 | |||
1119 | btmrvl_sdio_wakeup_fw(priv); | ||
1120 | sdio_claim_host(card->func); | ||
1121 | |||
1122 | for (loop = 0; loop < MAX_LOOP; loop++) { | ||
1123 | memset(buf, 0, sizeof(buf)); | ||
1124 | ptr = buf; | ||
1125 | |||
1126 | if (loop == 0) { | ||
1127 | /* Read the registers of SDIO function0 */ | ||
1128 | func = loop; | ||
1129 | reg_start = 0; | ||
1130 | reg_end = 9; | ||
1131 | } else { | ||
1132 | func = 2; | ||
1133 | reg_start = 0; | ||
1134 | reg_end = 0x09; | ||
1135 | } | ||
1136 | |||
1137 | ptr += sprintf(ptr, "SDIO Func%d (%#x-%#x): ", | ||
1138 | func, reg_start, reg_end); | ||
1139 | for (reg = reg_start; reg <= reg_end; reg++) { | ||
1140 | if (func == 0) | ||
1141 | data = sdio_f0_readb(card->func, reg, &ret); | ||
1142 | else | ||
1143 | data = sdio_readb(card->func, reg, &ret); | ||
1144 | |||
1145 | if (!ret) { | ||
1146 | ptr += sprintf(ptr, "%02x ", data); | ||
1147 | } else { | ||
1148 | ptr += sprintf(ptr, "ERR"); | ||
1149 | break; | ||
1150 | } | ||
1151 | } | ||
1152 | |||
1153 | BT_INFO("%s", buf); | ||
1154 | } | ||
1155 | |||
1156 | sdio_release_host(card->func); | ||
1157 | } | ||
1158 | |||
1159 | /* This function read/write firmware */ | ||
1160 | static enum | ||
1161 | rdwr_status btmrvl_sdio_rdwr_firmware(struct btmrvl_private *priv, | ||
1162 | u8 doneflag) | ||
1163 | { | ||
1164 | struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; | ||
1165 | int ret, tries; | ||
1166 | u8 ctrl_data = 0; | ||
1167 | |||
1168 | sdio_writeb(card->func, FW_DUMP_HOST_READY, card->reg->fw_dump_ctrl, | ||
1169 | &ret); | ||
1170 | |||
1171 | if (ret) { | ||
1172 | BT_ERR("SDIO write err"); | ||
1173 | return RDWR_STATUS_FAILURE; | ||
1174 | } | ||
1175 | |||
1176 | for (tries = 0; tries < MAX_POLL_TRIES; tries++) { | ||
1177 | ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl, | ||
1178 | &ret); | ||
1179 | |||
1180 | if (ret) { | ||
1181 | BT_ERR("SDIO read err"); | ||
1182 | return RDWR_STATUS_FAILURE; | ||
1183 | } | ||
1184 | |||
1185 | if (ctrl_data == FW_DUMP_DONE) | ||
1186 | break; | ||
1187 | if (doneflag && ctrl_data == doneflag) | ||
1188 | return RDWR_STATUS_DONE; | ||
1189 | if (ctrl_data != FW_DUMP_HOST_READY) { | ||
1190 | BT_INFO("The ctrl reg was changed, re-try again!"); | ||
1191 | sdio_writeb(card->func, FW_DUMP_HOST_READY, | ||
1192 | card->reg->fw_dump_ctrl, &ret); | ||
1193 | if (ret) { | ||
1194 | BT_ERR("SDIO write err"); | ||
1195 | return RDWR_STATUS_FAILURE; | ||
1196 | } | ||
1197 | } | ||
1198 | usleep_range(100, 200); | ||
1199 | } | ||
1200 | |||
1201 | if (ctrl_data == FW_DUMP_HOST_READY) { | ||
1202 | BT_ERR("Fail to pull ctrl_data"); | ||
1203 | return RDWR_STATUS_FAILURE; | ||
1204 | } | ||
1205 | |||
1206 | return RDWR_STATUS_SUCCESS; | ||
1207 | } | ||
1208 | |||
1209 | /* This function dump sdio register and memory data */ | ||
1210 | static void btmrvl_sdio_dump_firmware(struct btmrvl_private *priv) | ||
1211 | { | ||
1212 | struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; | ||
1213 | int ret = 0; | ||
1214 | unsigned int reg, reg_start, reg_end; | ||
1215 | enum rdwr_status stat; | ||
1216 | u8 *dbg_ptr, *end_ptr, *fw_dump_data, *fw_dump_ptr; | ||
1217 | u8 dump_num, idx, i, read_reg, doneflag = 0; | ||
1218 | u32 memory_size, fw_dump_len = 0; | ||
1219 | |||
1220 | /* dump sdio register first */ | ||
1221 | btmrvl_sdio_dump_regs(priv); | ||
1222 | |||
1223 | if (!card->supports_fw_dump) { | ||
1224 | BT_ERR("Firmware dump not supported for this card!"); | ||
1225 | return; | ||
1226 | } | ||
1227 | |||
1228 | for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) { | ||
1229 | struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; | ||
1230 | |||
1231 | if (entry->mem_ptr) { | ||
1232 | vfree(entry->mem_ptr); | ||
1233 | entry->mem_ptr = NULL; | ||
1234 | } | ||
1235 | entry->mem_size = 0; | ||
1236 | } | ||
1237 | |||
1238 | btmrvl_sdio_wakeup_fw(priv); | ||
1239 | sdio_claim_host(card->func); | ||
1240 | |||
1241 | BT_INFO("== btmrvl firmware dump start =="); | ||
1242 | |||
1243 | stat = btmrvl_sdio_rdwr_firmware(priv, doneflag); | ||
1244 | if (stat == RDWR_STATUS_FAILURE) | ||
1245 | goto done; | ||
1246 | |||
1247 | reg = card->reg->fw_dump_start; | ||
1248 | /* Read the number of the memories which will dump */ | ||
1249 | dump_num = sdio_readb(card->func, reg, &ret); | ||
1250 | |||
1251 | if (ret) { | ||
1252 | BT_ERR("SDIO read memory length err"); | ||
1253 | goto done; | ||
1254 | } | ||
1255 | |||
1256 | /* Read the length of every memory which will dump */ | ||
1257 | for (idx = 0; idx < dump_num; idx++) { | ||
1258 | struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; | ||
1259 | |||
1260 | stat = btmrvl_sdio_rdwr_firmware(priv, doneflag); | ||
1261 | if (stat == RDWR_STATUS_FAILURE) | ||
1262 | goto done; | ||
1263 | |||
1264 | memory_size = 0; | ||
1265 | reg = card->reg->fw_dump_start; | ||
1266 | for (i = 0; i < 4; i++) { | ||
1267 | read_reg = sdio_readb(card->func, reg, &ret); | ||
1268 | if (ret) { | ||
1269 | BT_ERR("SDIO read err"); | ||
1270 | goto done; | ||
1271 | } | ||
1272 | memory_size |= (read_reg << i*8); | ||
1273 | reg++; | ||
1274 | } | ||
1275 | |||
1276 | if (memory_size == 0) { | ||
1277 | BT_INFO("Firmware dump finished!"); | ||
1278 | break; | ||
1279 | } | ||
1280 | |||
1281 | BT_INFO("%s_SIZE=0x%x", entry->mem_name, memory_size); | ||
1282 | entry->mem_ptr = vzalloc(memory_size + 1); | ||
1283 | entry->mem_size = memory_size; | ||
1284 | if (!entry->mem_ptr) { | ||
1285 | BT_ERR("Vzalloc %s failed", entry->mem_name); | ||
1286 | goto done; | ||
1287 | } | ||
1288 | |||
1289 | fw_dump_len += (strlen("========Start dump ") + | ||
1290 | strlen(entry->mem_name) + | ||
1291 | strlen("========\n") + | ||
1292 | (memory_size + 1) + | ||
1293 | strlen("\n========End dump========\n")); | ||
1294 | |||
1295 | dbg_ptr = entry->mem_ptr; | ||
1296 | end_ptr = dbg_ptr + memory_size; | ||
1297 | |||
1298 | doneflag = entry->done_flag; | ||
1299 | BT_INFO("Start %s output, please wait...", | ||
1300 | entry->mem_name); | ||
1301 | |||
1302 | do { | ||
1303 | stat = btmrvl_sdio_rdwr_firmware(priv, doneflag); | ||
1304 | if (stat == RDWR_STATUS_FAILURE) | ||
1305 | goto done; | ||
1306 | |||
1307 | reg_start = card->reg->fw_dump_start; | ||
1308 | reg_end = card->reg->fw_dump_end; | ||
1309 | for (reg = reg_start; reg <= reg_end; reg++) { | ||
1310 | *dbg_ptr = sdio_readb(card->func, reg, &ret); | ||
1311 | if (ret) { | ||
1312 | BT_ERR("SDIO read err"); | ||
1313 | goto done; | ||
1314 | } | ||
1315 | if (dbg_ptr < end_ptr) | ||
1316 | dbg_ptr++; | ||
1317 | else | ||
1318 | BT_ERR("Allocated buffer not enough"); | ||
1319 | } | ||
1320 | |||
1321 | if (stat != RDWR_STATUS_DONE) { | ||
1322 | continue; | ||
1323 | } else { | ||
1324 | BT_INFO("%s done: size=0x%tx", | ||
1325 | entry->mem_name, | ||
1326 | dbg_ptr - entry->mem_ptr); | ||
1327 | break; | ||
1328 | } | ||
1329 | } while (1); | ||
1330 | } | ||
1331 | |||
1332 | BT_INFO("== btmrvl firmware dump end =="); | ||
1333 | |||
1334 | done: | ||
1335 | sdio_release_host(card->func); | ||
1336 | |||
1337 | if (fw_dump_len == 0) | ||
1338 | return; | ||
1339 | |||
1340 | fw_dump_data = vzalloc(fw_dump_len+1); | ||
1341 | if (!fw_dump_data) { | ||
1342 | BT_ERR("Vzalloc fw_dump_data fail!"); | ||
1343 | return; | ||
1344 | } | ||
1345 | fw_dump_ptr = fw_dump_data; | ||
1346 | |||
1347 | /* Dump all the memory data into single file, a userspace script will | ||
1348 | be used to split all the memory data to multiple files*/ | ||
1349 | BT_INFO("== btmrvl firmware dump to /sys/class/devcoredump start"); | ||
1350 | for (idx = 0; idx < dump_num; idx++) { | ||
1351 | struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; | ||
1352 | |||
1353 | if (entry->mem_ptr) { | ||
1354 | strcpy(fw_dump_ptr, "========Start dump "); | ||
1355 | fw_dump_ptr += strlen("========Start dump "); | ||
1356 | |||
1357 | strcpy(fw_dump_ptr, entry->mem_name); | ||
1358 | fw_dump_ptr += strlen(entry->mem_name); | ||
1359 | |||
1360 | strcpy(fw_dump_ptr, "========\n"); | ||
1361 | fw_dump_ptr += strlen("========\n"); | ||
1362 | |||
1363 | memcpy(fw_dump_ptr, entry->mem_ptr, entry->mem_size); | ||
1364 | fw_dump_ptr += entry->mem_size; | ||
1365 | |||
1366 | strcpy(fw_dump_ptr, "\n========End dump========\n"); | ||
1367 | fw_dump_ptr += strlen("\n========End dump========\n"); | ||
1368 | |||
1369 | vfree(mem_type_mapping_tbl[idx].mem_ptr); | ||
1370 | mem_type_mapping_tbl[idx].mem_ptr = NULL; | ||
1371 | } | ||
1372 | } | ||
1373 | |||
1374 | /* fw_dump_data will be free in device coredump release function | ||
1375 | after 5 min*/ | ||
1376 | dev_coredumpv(&priv->btmrvl_dev.hcidev->dev, fw_dump_data, | ||
1377 | fw_dump_len, GFP_KERNEL); | ||
1378 | BT_INFO("== btmrvl firmware dump to /sys/class/devcoredump end"); | ||
1379 | } | ||
1380 | |||
1083 | static int btmrvl_sdio_probe(struct sdio_func *func, | 1381 | static int btmrvl_sdio_probe(struct sdio_func *func, |
1084 | const struct sdio_device_id *id) | 1382 | const struct sdio_device_id *id) |
1085 | { | 1383 | { |
@@ -1103,6 +1401,7 @@ static int btmrvl_sdio_probe(struct sdio_func *func, | |||
1103 | card->reg = data->reg; | 1401 | card->reg = data->reg; |
1104 | card->sd_blksz_fw_dl = data->sd_blksz_fw_dl; | 1402 | card->sd_blksz_fw_dl = data->sd_blksz_fw_dl; |
1105 | card->support_pscan_win_report = data->support_pscan_win_report; | 1403 | card->support_pscan_win_report = data->support_pscan_win_report; |
1404 | card->supports_fw_dump = data->supports_fw_dump; | ||
1106 | } | 1405 | } |
1107 | 1406 | ||
1108 | if (btmrvl_sdio_register_dev(card) < 0) { | 1407 | if (btmrvl_sdio_register_dev(card) < 0) { |
@@ -1134,6 +1433,7 @@ static int btmrvl_sdio_probe(struct sdio_func *func, | |||
1134 | priv->hw_host_to_card = btmrvl_sdio_host_to_card; | 1433 | priv->hw_host_to_card = btmrvl_sdio_host_to_card; |
1135 | priv->hw_wakeup_firmware = btmrvl_sdio_wakeup_fw; | 1434 | priv->hw_wakeup_firmware = btmrvl_sdio_wakeup_fw; |
1136 | priv->hw_process_int_status = btmrvl_sdio_process_int_status; | 1435 | priv->hw_process_int_status = btmrvl_sdio_process_int_status; |
1436 | priv->firmware_dump = btmrvl_sdio_dump_firmware; | ||
1137 | 1437 | ||
1138 | if (btmrvl_register_hdev(priv)) { | 1438 | if (btmrvl_register_hdev(priv)) { |
1139 | BT_ERR("Register hdev failed!"); | 1439 | BT_ERR("Register hdev failed!"); |