diff options
Diffstat (limited to 'drivers/ide/ide-tape.c')
-rw-r--r-- | drivers/ide/ide-tape.c | 256 |
1 files changed, 58 insertions, 198 deletions
diff --git a/drivers/ide/ide-tape.c b/drivers/ide/ide-tape.c index 8226d52504d0..b2afbc7bcb6b 100644 --- a/drivers/ide/ide-tape.c +++ b/drivers/ide/ide-tape.c | |||
@@ -134,7 +134,6 @@ enum { | |||
134 | struct idetape_bh { | 134 | struct idetape_bh { |
135 | u32 b_size; | 135 | u32 b_size; |
136 | atomic_t b_count; | 136 | atomic_t b_count; |
137 | struct idetape_bh *b_reqnext; | ||
138 | char *b_data; | 137 | char *b_data; |
139 | }; | 138 | }; |
140 | 139 | ||
@@ -228,10 +227,6 @@ typedef struct ide_tape_obj { | |||
228 | char *b_data; | 227 | char *b_data; |
229 | int b_count; | 228 | int b_count; |
230 | 229 | ||
231 | int pages_per_buffer; | ||
232 | /* Wasted space in each stage */ | ||
233 | int excess_bh_size; | ||
234 | |||
235 | /* Measures average tape speed */ | 230 | /* Measures average tape speed */ |
236 | unsigned long avg_time; | 231 | unsigned long avg_time; |
237 | int avg_size; | 232 | int avg_size; |
@@ -303,9 +298,7 @@ static int idetape_input_buffers(ide_drive_t *drive, struct ide_atapi_pc *pc, | |||
303 | struct idetape_bh *bh = pc->bh; | 298 | struct idetape_bh *bh = pc->bh; |
304 | int count; | 299 | int count; |
305 | 300 | ||
306 | while (bcount) { | 301 | if (bcount && bh) { |
307 | if (bh == NULL) | ||
308 | break; | ||
309 | count = min( | 302 | count = min( |
310 | (unsigned int)(bh->b_size - atomic_read(&bh->b_count)), | 303 | (unsigned int)(bh->b_size - atomic_read(&bh->b_count)), |
311 | bcount); | 304 | bcount); |
@@ -313,15 +306,10 @@ static int idetape_input_buffers(ide_drive_t *drive, struct ide_atapi_pc *pc, | |||
313 | atomic_read(&bh->b_count), count); | 306 | atomic_read(&bh->b_count), count); |
314 | bcount -= count; | 307 | bcount -= count; |
315 | atomic_add(count, &bh->b_count); | 308 | atomic_add(count, &bh->b_count); |
316 | if (atomic_read(&bh->b_count) == bh->b_size) { | 309 | if (atomic_read(&bh->b_count) == bh->b_size) |
317 | bh = bh->b_reqnext; | 310 | pc->bh = NULL; |
318 | if (bh) | ||
319 | atomic_set(&bh->b_count, 0); | ||
320 | } | ||
321 | } | 311 | } |
322 | 312 | ||
323 | pc->bh = bh; | ||
324 | |||
325 | return bcount; | 313 | return bcount; |
326 | } | 314 | } |
327 | 315 | ||
@@ -331,22 +319,14 @@ static int idetape_output_buffers(ide_drive_t *drive, struct ide_atapi_pc *pc, | |||
331 | struct idetape_bh *bh = pc->bh; | 319 | struct idetape_bh *bh = pc->bh; |
332 | int count; | 320 | int count; |
333 | 321 | ||
334 | while (bcount) { | 322 | if (bcount && bh) { |
335 | if (bh == NULL) | ||
336 | break; | ||
337 | count = min((unsigned int)pc->b_count, (unsigned int)bcount); | 323 | count = min((unsigned int)pc->b_count, (unsigned int)bcount); |
338 | drive->hwif->tp_ops->output_data(drive, NULL, pc->b_data, count); | 324 | drive->hwif->tp_ops->output_data(drive, NULL, pc->b_data, count); |
339 | bcount -= count; | 325 | bcount -= count; |
340 | pc->b_data += count; | 326 | pc->b_data += count; |
341 | pc->b_count -= count; | 327 | pc->b_count -= count; |
342 | if (!pc->b_count) { | 328 | if (!pc->b_count) |
343 | bh = bh->b_reqnext; | 329 | pc->bh = NULL; |
344 | pc->bh = bh; | ||
345 | if (bh) { | ||
346 | pc->b_data = bh->b_data; | ||
347 | pc->b_count = atomic_read(&bh->b_count); | ||
348 | } | ||
349 | } | ||
350 | } | 330 | } |
351 | 331 | ||
352 | return bcount; | 332 | return bcount; |
@@ -355,24 +335,20 @@ static int idetape_output_buffers(ide_drive_t *drive, struct ide_atapi_pc *pc, | |||
355 | static void idetape_update_buffers(ide_drive_t *drive, struct ide_atapi_pc *pc) | 335 | static void idetape_update_buffers(ide_drive_t *drive, struct ide_atapi_pc *pc) |
356 | { | 336 | { |
357 | struct idetape_bh *bh = pc->bh; | 337 | struct idetape_bh *bh = pc->bh; |
358 | int count; | ||
359 | unsigned int bcount = pc->xferred; | 338 | unsigned int bcount = pc->xferred; |
360 | 339 | ||
361 | if (pc->flags & PC_FLAG_WRITING) | 340 | if (pc->flags & PC_FLAG_WRITING) |
362 | return; | 341 | return; |
363 | while (bcount) { | 342 | if (bcount) { |
364 | if (bh == NULL) { | 343 | if (bh == NULL || bcount > bh->b_size) { |
365 | printk(KERN_ERR "ide-tape: bh == NULL in %s\n", | 344 | printk(KERN_ERR "ide-tape: bh == NULL in %s\n", |
366 | __func__); | 345 | __func__); |
367 | return; | 346 | return; |
368 | } | 347 | } |
369 | count = min((unsigned int)bh->b_size, (unsigned int)bcount); | 348 | atomic_set(&bh->b_count, bcount); |
370 | atomic_set(&bh->b_count, count); | ||
371 | if (atomic_read(&bh->b_count) == bh->b_size) | 349 | if (atomic_read(&bh->b_count) == bh->b_size) |
372 | bh = bh->b_reqnext; | 350 | pc->bh = NULL; |
373 | bcount -= count; | ||
374 | } | 351 | } |
375 | pc->bh = bh; | ||
376 | } | 352 | } |
377 | 353 | ||
378 | /* | 354 | /* |
@@ -439,24 +415,10 @@ static void idetape_analyze_error(ide_drive_t *drive, u8 *sense) | |||
439 | /* Free data buffers completely. */ | 415 | /* Free data buffers completely. */ |
440 | static void ide_tape_kfree_buffer(idetape_tape_t *tape) | 416 | static void ide_tape_kfree_buffer(idetape_tape_t *tape) |
441 | { | 417 | { |
442 | struct idetape_bh *prev_bh, *bh = tape->merge_bh; | 418 | struct idetape_bh *bh = tape->merge_bh; |
443 | |||
444 | while (bh) { | ||
445 | u32 size = bh->b_size; | ||
446 | |||
447 | while (size) { | ||
448 | unsigned int order = fls(size >> PAGE_SHIFT)-1; | ||
449 | |||
450 | if (bh->b_data) | ||
451 | free_pages((unsigned long)bh->b_data, order); | ||
452 | 419 | ||
453 | size &= (order-1); | 420 | kfree(bh->b_data); |
454 | bh->b_data += (1 << order) * PAGE_SIZE; | 421 | kfree(bh); |
455 | } | ||
456 | prev_bh = bh; | ||
457 | bh = bh->b_reqnext; | ||
458 | kfree(prev_bh); | ||
459 | } | ||
460 | } | 422 | } |
461 | 423 | ||
462 | static void ide_tape_handle_dsc(ide_drive_t *); | 424 | static void ide_tape_handle_dsc(ide_drive_t *); |
@@ -861,117 +823,50 @@ out: | |||
861 | } | 823 | } |
862 | 824 | ||
863 | /* | 825 | /* |
864 | * The function below uses __get_free_pages to allocate a data buffer of size | 826 | * It returns a pointer to the newly allocated buffer, or NULL in case |
865 | * tape->buffer_size (or a bit more). We attempt to combine sequential pages as | 827 | * of failure. |
866 | * much as possible. | ||
867 | * | ||
868 | * It returns a pointer to the newly allocated buffer, or NULL in case of | ||
869 | * failure. | ||
870 | */ | 828 | */ |
871 | static struct idetape_bh *ide_tape_kmalloc_buffer(idetape_tape_t *tape, | 829 | static struct idetape_bh *ide_tape_kmalloc_buffer(idetape_tape_t *tape, |
872 | int full, int clear) | 830 | int full) |
873 | { | 831 | { |
874 | struct idetape_bh *prev_bh, *bh, *merge_bh; | 832 | struct idetape_bh *bh; |
875 | int pages = tape->pages_per_buffer; | ||
876 | unsigned int order, b_allocd; | ||
877 | char *b_data = NULL; | ||
878 | |||
879 | merge_bh = kmalloc(sizeof(struct idetape_bh), GFP_KERNEL); | ||
880 | bh = merge_bh; | ||
881 | if (bh == NULL) | ||
882 | goto abort; | ||
883 | |||
884 | order = fls(pages) - 1; | ||
885 | bh->b_data = (char *) __get_free_pages(GFP_KERNEL, order); | ||
886 | if (!bh->b_data) | ||
887 | goto abort; | ||
888 | b_allocd = (1 << order) * PAGE_SIZE; | ||
889 | pages &= (order-1); | ||
890 | |||
891 | if (clear) | ||
892 | memset(bh->b_data, 0, b_allocd); | ||
893 | bh->b_reqnext = NULL; | ||
894 | bh->b_size = b_allocd; | ||
895 | atomic_set(&bh->b_count, full ? bh->b_size : 0); | ||
896 | 833 | ||
897 | while (pages) { | 834 | bh = kmalloc(sizeof(struct idetape_bh), GFP_KERNEL); |
898 | order = fls(pages) - 1; | 835 | if (!bh) |
899 | b_data = (char *) __get_free_pages(GFP_KERNEL, order); | 836 | return NULL; |
900 | if (!b_data) | ||
901 | goto abort; | ||
902 | b_allocd = (1 << order) * PAGE_SIZE; | ||
903 | |||
904 | if (clear) | ||
905 | memset(b_data, 0, b_allocd); | ||
906 | |||
907 | /* newly allocated page frames below buffer header or ...*/ | ||
908 | if (bh->b_data == b_data + b_allocd) { | ||
909 | bh->b_size += b_allocd; | ||
910 | bh->b_data -= b_allocd; | ||
911 | if (full) | ||
912 | atomic_add(b_allocd, &bh->b_count); | ||
913 | continue; | ||
914 | } | ||
915 | /* they are above the header */ | ||
916 | if (b_data == bh->b_data + bh->b_size) { | ||
917 | bh->b_size += b_allocd; | ||
918 | if (full) | ||
919 | atomic_add(b_allocd, &bh->b_count); | ||
920 | continue; | ||
921 | } | ||
922 | prev_bh = bh; | ||
923 | bh = kmalloc(sizeof(struct idetape_bh), GFP_KERNEL); | ||
924 | if (!bh) { | ||
925 | free_pages((unsigned long) b_data, order); | ||
926 | goto abort; | ||
927 | } | ||
928 | bh->b_reqnext = NULL; | ||
929 | bh->b_data = b_data; | ||
930 | bh->b_size = b_allocd; | ||
931 | atomic_set(&bh->b_count, full ? bh->b_size : 0); | ||
932 | prev_bh->b_reqnext = bh; | ||
933 | 837 | ||
934 | pages &= (order-1); | 838 | bh->b_data = kmalloc(tape->buffer_size, GFP_KERNEL); |
839 | if (!bh->b_data) { | ||
840 | kfree(bh); | ||
841 | return NULL; | ||
935 | } | 842 | } |
936 | 843 | ||
937 | bh->b_size -= tape->excess_bh_size; | 844 | bh->b_size = tape->buffer_size; |
938 | if (full) | 845 | atomic_set(&bh->b_count, full ? bh->b_size : 0); |
939 | atomic_sub(tape->excess_bh_size, &bh->b_count); | 846 | |
940 | return merge_bh; | 847 | return bh; |
941 | abort: | ||
942 | ide_tape_kfree_buffer(tape); | ||
943 | return NULL; | ||
944 | } | 848 | } |
945 | 849 | ||
946 | static int idetape_copy_stage_from_user(idetape_tape_t *tape, | 850 | static int idetape_copy_stage_from_user(idetape_tape_t *tape, |
947 | const char __user *buf, int n) | 851 | const char __user *buf, int n) |
948 | { | 852 | { |
949 | struct idetape_bh *bh = tape->bh; | 853 | struct idetape_bh *bh = tape->bh; |
950 | int count; | ||
951 | int ret = 0; | 854 | int ret = 0; |
952 | 855 | ||
953 | while (n) { | 856 | if (n) { |
954 | if (bh == NULL) { | 857 | if (bh == NULL || n > bh->b_size - atomic_read(&bh->b_count)) { |
955 | printk(KERN_ERR "ide-tape: bh == NULL in %s\n", | 858 | printk(KERN_ERR "ide-tape: bh == NULL in %s\n", |
956 | __func__); | 859 | __func__); |
957 | return 1; | 860 | return 1; |
958 | } | 861 | } |
959 | count = min((unsigned int) | ||
960 | (bh->b_size - atomic_read(&bh->b_count)), | ||
961 | (unsigned int)n); | ||
962 | if (copy_from_user(bh->b_data + atomic_read(&bh->b_count), buf, | 862 | if (copy_from_user(bh->b_data + atomic_read(&bh->b_count), buf, |
963 | count)) | 863 | n)) |
964 | ret = 1; | 864 | ret = 1; |
965 | n -= count; | 865 | atomic_add(n, &bh->b_count); |
966 | atomic_add(count, &bh->b_count); | 866 | if (atomic_read(&bh->b_count) == bh->b_size) |
967 | buf += count; | 867 | tape->bh = NULL; |
968 | if (atomic_read(&bh->b_count) == bh->b_size) { | ||
969 | bh = bh->b_reqnext; | ||
970 | if (bh) | ||
971 | atomic_set(&bh->b_count, 0); | ||
972 | } | ||
973 | } | 868 | } |
974 | tape->bh = bh; | 869 | |
975 | return ret; | 870 | return ret; |
976 | } | 871 | } |
977 | 872 | ||
@@ -979,30 +874,20 @@ static int idetape_copy_stage_to_user(idetape_tape_t *tape, char __user *buf, | |||
979 | int n) | 874 | int n) |
980 | { | 875 | { |
981 | struct idetape_bh *bh = tape->bh; | 876 | struct idetape_bh *bh = tape->bh; |
982 | int count; | ||
983 | int ret = 0; | 877 | int ret = 0; |
984 | 878 | ||
985 | while (n) { | 879 | if (n) { |
986 | if (bh == NULL) { | 880 | if (bh == NULL || n > tape->b_count) { |
987 | printk(KERN_ERR "ide-tape: bh == NULL in %s\n", | 881 | printk(KERN_ERR "ide-tape: bh == NULL in %s\n", |
988 | __func__); | 882 | __func__); |
989 | return 1; | 883 | return 1; |
990 | } | 884 | } |
991 | count = min(tape->b_count, n); | 885 | if (copy_to_user(buf, tape->b_data, n)) |
992 | if (copy_to_user(buf, tape->b_data, count)) | ||
993 | ret = 1; | 886 | ret = 1; |
994 | n -= count; | 887 | tape->b_data += n; |
995 | tape->b_data += count; | 888 | tape->b_count -= n; |
996 | tape->b_count -= count; | 889 | if (!tape->b_count) |
997 | buf += count; | 890 | tape->bh = NULL; |
998 | if (!tape->b_count) { | ||
999 | bh = bh->b_reqnext; | ||
1000 | tape->bh = bh; | ||
1001 | if (bh) { | ||
1002 | tape->b_data = bh->b_data; | ||
1003 | tape->b_count = atomic_read(&bh->b_count); | ||
1004 | } | ||
1005 | } | ||
1006 | } | 891 | } |
1007 | return ret; | 892 | return ret; |
1008 | } | 893 | } |
@@ -1254,7 +1139,7 @@ static int idetape_add_chrdev_write_request(ide_drive_t *drive, int blocks) | |||
1254 | static void ide_tape_flush_merge_buffer(ide_drive_t *drive) | 1139 | static void ide_tape_flush_merge_buffer(ide_drive_t *drive) |
1255 | { | 1140 | { |
1256 | idetape_tape_t *tape = drive->driver_data; | 1141 | idetape_tape_t *tape = drive->driver_data; |
1257 | int blocks, min; | 1142 | int blocks; |
1258 | struct idetape_bh *bh; | 1143 | struct idetape_bh *bh; |
1259 | 1144 | ||
1260 | if (tape->chrdev_dir != IDETAPE_DIR_WRITE) { | 1145 | if (tape->chrdev_dir != IDETAPE_DIR_WRITE) { |
@@ -1269,31 +1154,16 @@ static void ide_tape_flush_merge_buffer(ide_drive_t *drive) | |||
1269 | if (tape->merge_bh_size) { | 1154 | if (tape->merge_bh_size) { |
1270 | blocks = tape->merge_bh_size / tape->blk_size; | 1155 | blocks = tape->merge_bh_size / tape->blk_size; |
1271 | if (tape->merge_bh_size % tape->blk_size) { | 1156 | if (tape->merge_bh_size % tape->blk_size) { |
1272 | unsigned int i; | 1157 | unsigned int i = tape->blk_size - |
1273 | 1158 | tape->merge_bh_size % tape->blk_size; | |
1274 | blocks++; | 1159 | blocks++; |
1275 | i = tape->blk_size - tape->merge_bh_size % | ||
1276 | tape->blk_size; | ||
1277 | bh = tape->bh->b_reqnext; | ||
1278 | while (bh) { | ||
1279 | atomic_set(&bh->b_count, 0); | ||
1280 | bh = bh->b_reqnext; | ||
1281 | } | ||
1282 | bh = tape->bh; | 1160 | bh = tape->bh; |
1283 | while (i) { | 1161 | if (bh) { |
1284 | if (bh == NULL) { | ||
1285 | printk(KERN_INFO "ide-tape: bug," | ||
1286 | " bh NULL\n"); | ||
1287 | break; | ||
1288 | } | ||
1289 | min = min(i, (unsigned int)(bh->b_size - | ||
1290 | atomic_read(&bh->b_count))); | ||
1291 | memset(bh->b_data + atomic_read(&bh->b_count), | 1162 | memset(bh->b_data + atomic_read(&bh->b_count), |
1292 | 0, min); | 1163 | 0, i); |
1293 | atomic_add(min, &bh->b_count); | 1164 | atomic_add(i, &bh->b_count); |
1294 | i -= min; | 1165 | } else |
1295 | bh = bh->b_reqnext; | 1166 | printk(KERN_INFO "ide-tape: bug, bh NULL\n"); |
1296 | } | ||
1297 | } | 1167 | } |
1298 | (void) idetape_add_chrdev_write_request(drive, blocks); | 1168 | (void) idetape_add_chrdev_write_request(drive, blocks); |
1299 | tape->merge_bh_size = 0; | 1169 | tape->merge_bh_size = 0; |
@@ -1321,7 +1191,7 @@ static int idetape_init_read(ide_drive_t *drive) | |||
1321 | " 0 now\n"); | 1191 | " 0 now\n"); |
1322 | tape->merge_bh_size = 0; | 1192 | tape->merge_bh_size = 0; |
1323 | } | 1193 | } |
1324 | tape->merge_bh = ide_tape_kmalloc_buffer(tape, 0, 0); | 1194 | tape->merge_bh = ide_tape_kmalloc_buffer(tape, 0); |
1325 | if (!tape->merge_bh) | 1195 | if (!tape->merge_bh) |
1326 | return -ENOMEM; | 1196 | return -ENOMEM; |
1327 | tape->chrdev_dir = IDETAPE_DIR_READ; | 1197 | tape->chrdev_dir = IDETAPE_DIR_READ; |
@@ -1368,23 +1238,18 @@ static int idetape_add_chrdev_read_request(ide_drive_t *drive, int blocks) | |||
1368 | static void idetape_pad_zeros(ide_drive_t *drive, int bcount) | 1238 | static void idetape_pad_zeros(ide_drive_t *drive, int bcount) |
1369 | { | 1239 | { |
1370 | idetape_tape_t *tape = drive->driver_data; | 1240 | idetape_tape_t *tape = drive->driver_data; |
1371 | struct idetape_bh *bh; | 1241 | struct idetape_bh *bh = tape->merge_bh; |
1372 | int blocks; | 1242 | int blocks; |
1373 | 1243 | ||
1374 | while (bcount) { | 1244 | while (bcount) { |
1375 | unsigned int count; | 1245 | unsigned int count; |
1376 | 1246 | ||
1377 | bh = tape->merge_bh; | ||
1378 | count = min(tape->buffer_size, bcount); | 1247 | count = min(tape->buffer_size, bcount); |
1379 | bcount -= count; | 1248 | bcount -= count; |
1380 | blocks = count / tape->blk_size; | 1249 | blocks = count / tape->blk_size; |
1381 | while (count) { | 1250 | atomic_set(&bh->b_count, count); |
1382 | atomic_set(&bh->b_count, | 1251 | memset(bh->b_data, 0, atomic_read(&bh->b_count)); |
1383 | min(count, (unsigned int)bh->b_size)); | 1252 | |
1384 | memset(bh->b_data, 0, atomic_read(&bh->b_count)); | ||
1385 | count -= atomic_read(&bh->b_count); | ||
1386 | bh = bh->b_reqnext; | ||
1387 | } | ||
1388 | idetape_queue_rw_tail(drive, REQ_IDETAPE_WRITE, blocks, | 1253 | idetape_queue_rw_tail(drive, REQ_IDETAPE_WRITE, blocks, |
1389 | tape->merge_bh); | 1254 | tape->merge_bh); |
1390 | } | 1255 | } |
@@ -1596,7 +1461,7 @@ static ssize_t idetape_chrdev_write(struct file *file, const char __user *buf, | |||
1596 | "should be 0 now\n"); | 1461 | "should be 0 now\n"); |
1597 | tape->merge_bh_size = 0; | 1462 | tape->merge_bh_size = 0; |
1598 | } | 1463 | } |
1599 | tape->merge_bh = ide_tape_kmalloc_buffer(tape, 0, 0); | 1464 | tape->merge_bh = ide_tape_kmalloc_buffer(tape, 0); |
1600 | if (!tape->merge_bh) | 1465 | if (!tape->merge_bh) |
1601 | return -ENOMEM; | 1466 | return -ENOMEM; |
1602 | tape->chrdev_dir = IDETAPE_DIR_WRITE; | 1467 | tape->chrdev_dir = IDETAPE_DIR_WRITE; |
@@ -1970,7 +1835,7 @@ static void idetape_write_release(ide_drive_t *drive, unsigned int minor) | |||
1970 | idetape_tape_t *tape = drive->driver_data; | 1835 | idetape_tape_t *tape = drive->driver_data; |
1971 | 1836 | ||
1972 | ide_tape_flush_merge_buffer(drive); | 1837 | ide_tape_flush_merge_buffer(drive); |
1973 | tape->merge_bh = ide_tape_kmalloc_buffer(tape, 1, 0); | 1838 | tape->merge_bh = ide_tape_kmalloc_buffer(tape, 1); |
1974 | if (tape->merge_bh != NULL) { | 1839 | if (tape->merge_bh != NULL) { |
1975 | idetape_pad_zeros(drive, tape->blk_size * | 1840 | idetape_pad_zeros(drive, tape->blk_size * |
1976 | (tape->user_bs_factor - 1)); | 1841 | (tape->user_bs_factor - 1)); |
@@ -2201,11 +2066,6 @@ static void idetape_setup(ide_drive_t *drive, idetape_tape_t *tape, int minor) | |||
2201 | tape->buffer_size = *ctl * tape->blk_size; | 2066 | tape->buffer_size = *ctl * tape->blk_size; |
2202 | } | 2067 | } |
2203 | buffer_size = tape->buffer_size; | 2068 | buffer_size = tape->buffer_size; |
2204 | tape->pages_per_buffer = buffer_size / PAGE_SIZE; | ||
2205 | if (buffer_size % PAGE_SIZE) { | ||
2206 | tape->pages_per_buffer++; | ||
2207 | tape->excess_bh_size = PAGE_SIZE - buffer_size % PAGE_SIZE; | ||
2208 | } | ||
2209 | 2069 | ||
2210 | /* select the "best" DSC read/write polling freq */ | 2070 | /* select the "best" DSC read/write polling freq */ |
2211 | speed = max(*(u16 *)&tape->caps[14], *(u16 *)&tape->caps[8]); | 2071 | speed = max(*(u16 *)&tape->caps[14], *(u16 *)&tape->caps[8]); |