Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PM: hibernate: Enforce ordering during image compression/decompression

An S4 (suspend to disk) test on the LoongArch 3A6000 platform sometimes
fails with the following error messaged in the dmesg log:

Invalid LZO compressed length

That happens because when compressing/decompressing the image, the
synchronization between the control thread and the compress/decompress/crc
thread is based on a relaxed ordering interface, which is unreliable, and the
following situation may occur:

CPU 0 CPU 1
save_image_lzo lzo_compress_threadfn
atomic_set(&d->stop, 1);
atomic_read(&data[thr].stop)
data[thr].cmp = data[thr].cmp_len;
WRITE data[thr].cmp_len

Then CPU0 gets a stale cmp_len and writes it to disk. During resume from S4,
wrong cmp_len is loaded.

To maintain data consistency between the two threads, use the acquire/release
variants of atomic set and read operations.

Fixes: 081a9d043c98 ("PM / Hibernate: Improve performance of LZO/plain hibernation, checksum image")
Cc: All applicable <stable@vger.kernel.org>
Signed-off-by: Hongchen Zhang <zhanghongchen@loongson.cn>
Co-developed-by: Weihao Li <liweihao@loongson.cn>
Signed-off-by: Weihao Li <liweihao@loongson.cn>
[ rjw: Subject rewrite and changelog edits ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Hongchen Zhang and committed by
Rafael J. Wysocki
71cd7e80 0c4cae1b

+19 -19
+19 -19
kernel/power/swap.c
··· 606 606 unsigned i; 607 607 608 608 while (1) { 609 - wait_event(d->go, atomic_read(&d->ready) || 609 + wait_event(d->go, atomic_read_acquire(&d->ready) || 610 610 kthread_should_stop()); 611 611 if (kthread_should_stop()) { 612 612 d->thr = NULL; 613 - atomic_set(&d->stop, 1); 613 + atomic_set_release(&d->stop, 1); 614 614 wake_up(&d->done); 615 615 break; 616 616 } ··· 619 619 for (i = 0; i < d->run_threads; i++) 620 620 *d->crc32 = crc32_le(*d->crc32, 621 621 d->unc[i], *d->unc_len[i]); 622 - atomic_set(&d->stop, 1); 622 + atomic_set_release(&d->stop, 1); 623 623 wake_up(&d->done); 624 624 } 625 625 return 0; ··· 649 649 struct cmp_data *d = data; 650 650 651 651 while (1) { 652 - wait_event(d->go, atomic_read(&d->ready) || 652 + wait_event(d->go, atomic_read_acquire(&d->ready) || 653 653 kthread_should_stop()); 654 654 if (kthread_should_stop()) { 655 655 d->thr = NULL; 656 656 d->ret = -1; 657 - atomic_set(&d->stop, 1); 657 + atomic_set_release(&d->stop, 1); 658 658 wake_up(&d->done); 659 659 break; 660 660 } ··· 663 663 d->ret = lzo1x_1_compress(d->unc, d->unc_len, 664 664 d->cmp + LZO_HEADER, &d->cmp_len, 665 665 d->wrk); 666 - atomic_set(&d->stop, 1); 666 + atomic_set_release(&d->stop, 1); 667 667 wake_up(&d->done); 668 668 } 669 669 return 0; ··· 798 798 799 799 data[thr].unc_len = off; 800 800 801 - atomic_set(&data[thr].ready, 1); 801 + atomic_set_release(&data[thr].ready, 1); 802 802 wake_up(&data[thr].go); 803 803 } 804 804 ··· 806 806 break; 807 807 808 808 crc->run_threads = thr; 809 - atomic_set(&crc->ready, 1); 809 + atomic_set_release(&crc->ready, 1); 810 810 wake_up(&crc->go); 811 811 812 812 for (run_threads = thr, thr = 0; thr < run_threads; thr++) { 813 813 wait_event(data[thr].done, 814 - atomic_read(&data[thr].stop)); 814 + atomic_read_acquire(&data[thr].stop)); 815 815 atomic_set(&data[thr].stop, 0); 816 816 817 817 ret = data[thr].ret; ··· 850 850 } 851 851 } 852 852 853 - wait_event(crc->done, atomic_read(&crc->stop)); 853 + wait_event(crc->done, atomic_read_acquire(&crc->stop)); 854 854 atomic_set(&crc->stop, 0); 855 855 } 856 856 ··· 1132 1132 struct dec_data *d = data; 1133 1133 1134 1134 while (1) { 1135 - wait_event(d->go, atomic_read(&d->ready) || 1135 + wait_event(d->go, atomic_read_acquire(&d->ready) || 1136 1136 kthread_should_stop()); 1137 1137 if (kthread_should_stop()) { 1138 1138 d->thr = NULL; 1139 1139 d->ret = -1; 1140 - atomic_set(&d->stop, 1); 1140 + atomic_set_release(&d->stop, 1); 1141 1141 wake_up(&d->done); 1142 1142 break; 1143 1143 } ··· 1150 1150 flush_icache_range((unsigned long)d->unc, 1151 1151 (unsigned long)d->unc + d->unc_len); 1152 1152 1153 - atomic_set(&d->stop, 1); 1153 + atomic_set_release(&d->stop, 1); 1154 1154 wake_up(&d->done); 1155 1155 } 1156 1156 return 0; ··· 1335 1335 } 1336 1336 1337 1337 if (crc->run_threads) { 1338 - wait_event(crc->done, atomic_read(&crc->stop)); 1338 + wait_event(crc->done, atomic_read_acquire(&crc->stop)); 1339 1339 atomic_set(&crc->stop, 0); 1340 1340 crc->run_threads = 0; 1341 1341 } ··· 1371 1371 pg = 0; 1372 1372 } 1373 1373 1374 - atomic_set(&data[thr].ready, 1); 1374 + atomic_set_release(&data[thr].ready, 1); 1375 1375 wake_up(&data[thr].go); 1376 1376 } 1377 1377 ··· 1390 1390 1391 1391 for (run_threads = thr, thr = 0; thr < run_threads; thr++) { 1392 1392 wait_event(data[thr].done, 1393 - atomic_read(&data[thr].stop)); 1393 + atomic_read_acquire(&data[thr].stop)); 1394 1394 atomic_set(&data[thr].stop, 0); 1395 1395 1396 1396 ret = data[thr].ret; ··· 1421 1421 ret = snapshot_write_next(snapshot); 1422 1422 if (ret <= 0) { 1423 1423 crc->run_threads = thr + 1; 1424 - atomic_set(&crc->ready, 1); 1424 + atomic_set_release(&crc->ready, 1); 1425 1425 wake_up(&crc->go); 1426 1426 goto out_finish; 1427 1427 } ··· 1429 1429 } 1430 1430 1431 1431 crc->run_threads = thr; 1432 - atomic_set(&crc->ready, 1); 1432 + atomic_set_release(&crc->ready, 1); 1433 1433 wake_up(&crc->go); 1434 1434 } 1435 1435 1436 1436 out_finish: 1437 1437 if (crc->run_threads) { 1438 - wait_event(crc->done, atomic_read(&crc->stop)); 1438 + wait_event(crc->done, atomic_read_acquire(&crc->stop)); 1439 1439 atomic_set(&crc->stop, 0); 1440 1440 } 1441 1441 stop = ktime_get();