summaryrefslogtreecommitdiff
path: root/drivers/block/drbd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/block/drbd')
-rw-r--r--drivers/block/drbd/drbd_bitmap.c25
1 files changed, 22 insertions, 3 deletions
diff --git a/drivers/block/drbd/drbd_bitmap.c b/drivers/block/drbd/drbd_bitmap.c
index 9390e9526786..c5361487cf47 100644
--- a/drivers/block/drbd/drbd_bitmap.c
+++ b/drivers/block/drbd/drbd_bitmap.c
@@ -376,9 +376,16 @@ static unsigned long __bm_count_bits(struct drbd_bitmap *b, const int swap_endia
unsigned long *p_addr, *bm, offset = 0;
unsigned long bits = 0;
unsigned long i, do_now;
+ unsigned long words;
- while (offset < b->bm_words) {
- i = do_now = min_t(size_t, b->bm_words-offset, LWPP);
+ /* due to 64bit alignment, the last long on a 32bit arch
+ * may be not used at all. The last used long will likely
+ * be only partially used, always. Don't count those bits,
+ * but mask them out. */
+ words = (b->bm_bits + BITS_PER_LONG - 1) >> LN2_BPL;
+
+ while (offset < words) {
+ i = do_now = min_t(size_t, words-offset, LWPP);
p_addr = __bm_map_paddr(b, offset, KM_USER0);
bm = p_addr + MLPP(offset);
while (i--) {
@@ -388,8 +395,20 @@ static unsigned long __bm_count_bits(struct drbd_bitmap *b, const int swap_endia
#endif
bits += hweight_long(*bm++);
}
- __bm_unmap(p_addr, KM_USER0);
offset += do_now;
+ if (offset == words) {
+ /* last word may only be partially used,
+ * see also bm_clear_surplus. */
+ i = (1UL << (b->bm_bits & (BITS_PER_LONG-1))) -1;
+ if (i) {
+ bits -= hweight_long(p_addr[do_now-1] & ~i);
+ p_addr[do_now-1] &= i;
+ }
+ /* 32bit arch, may have an unused padding long */
+ if (words != b->bm_words)
+ p_addr[do_now] = 0;
+ }
+ __bm_unmap(p_addr, KM_USER0);
cond_resched();
}