2 * Copyright(c) 2019 Intel Corporation
3 * SPDX-License-Identifier: BSD-3-Clause-Clear
6 #include "../../ocf_priv.h"
10 /* Implementation of hashmap-ish structure for tracking core lines in nhit
11 * promotion policy. It consists of two arrays:
12 * - hash_map - indexed by hash formed from core id and core lba pairs,
13 * contains pointers (indices) to the ring buffer. Each index in this array
15 * - ring_buffer - contains per-coreline metadata and collision info for
16 * open addressing. If we run out of space in this array, we just loop around
17 * and insert elements from the beggining. So lifetime of a core line varies
18 * depending on insertion and removal rate.
20 * and rb_pointer which is index to ring_buffer element that is going to be used
24 * - query(core_id, core_lba):
25 * Check if core line is present in structure, bump up counter and
28 * - insertion(core_id, core_lba):
29 * Insert new core line into structure
30 * 1. get new slot from ring buffer
31 * a. check if current slot under rb_pointer is valid
33 * b. set current slot as invalid and increment rb_pointer
34 * 2. lock hash bucket for new item and for ring buffer slot
35 * (if non-empty) in ascending bucket id order (to avoid deadlock)
36 * 3. insert new data, add to collision
37 * 4. unlock both hash buckets
38 * 5. commit rb_slot (mark it as valid)
40 * Insertion explained visually:
42 * Suppose that we want to add a new core line with hash value H which already has
43 * some colliding core lines
45 * hash(core_id, core_lba)
49 * +--+--+--+--+--+--+--+--++-+--+
50 * | | |I | | | | | |H | | hash_map
51 * +--+--++-+--+--+--+--+--++-+--+
52 * __| rb_pointer | _______
55 * +--++-+--+---+-+--+--+--++-+--+--++-+--++-+
56 * | | | | |X | | | | | | | | | | ring_buffer
57 * +--++-+--+---+-+--+--+--++-+--+--++-+--+--+
59 * |________| |________|
61 * We will attempt to insert new element at rb_pointer. Since rb_pointer is
62 * pointing to occupied rb slot we need to write-lock hash bucket I associated
63 * with this slot and remove it from collision list. We've gained an empty slot
64 * and we use slot X for new hash H entry.
66 * +--+--+--+--+--+--+--+--+--+--+
67 * | | |I | | | | | |H | | hash_map
68 * +--+--++-+--+--+--+--+--++-+--+
69 * __| rb_pointer | _______
72 * +--++-+--+-----++-+--+--++-+--+--++-+--++-+
73 * | | | | |X | | | | | | | | | | ring_buffer
74 * +--+--+--+---+-+--+--+--++-+--+--++-+--++-+
77 * |__________________________|
79 * Valid field in nhit_list_elem is guarded by rb_pointer_lock to make sure we
80 * won't try to use the same slot in two threads. That would be possible if in
81 * time between removal from collision and insertion into the new one the
82 * rb_pointer would go around the whole structure (likeliness depends on size of
86 #define HASH_PRIME 4099
88 struct nhit_list_elem
{
89 /* Fields are ordered for memory efficiency, not for looks. */
92 ocf_cache_line_t coll_prev
;
93 ocf_cache_line_t coll_next
;
94 ocf_core_id_t core_id
;
99 ocf_cache_line_t hash_entries
;
102 ocf_cache_line_t
*hash_map
;
103 env_rwsem
*hash_locks
;
105 struct nhit_list_elem
*ring_buffer
;
107 env_spinlock rb_pointer_lock
;
110 static uint64_t calculate_hash_buckets(uint64_t hash_size
)
112 return OCF_DIV_ROUND_UP(hash_size
/ 4, HASH_PRIME
) * HASH_PRIME
- 1;
115 uint64_t nhit_hash_sizeof(uint64_t hash_size
)
118 uint64_t n_buckets
= calculate_hash_buckets(hash_size
);
120 size
+= sizeof(struct nhit_hash
);
122 size
+= n_buckets
* sizeof(ocf_cache_line_t
);
123 size
+= n_buckets
* sizeof(env_rwsem
);
125 size
+= hash_size
* sizeof(struct nhit_list_elem
);
130 ocf_error_t
nhit_hash_init(uint64_t hash_size
, nhit_hash_t
*ctx
)
133 struct nhit_hash
*new_ctx
;
137 new_ctx
= env_vzalloc(sizeof(*new_ctx
));
139 result
= -OCF_ERR_NO_MEM
;
143 new_ctx
->rb_entries
= hash_size
;
144 new_ctx
->hash_entries
= calculate_hash_buckets(hash_size
);
146 new_ctx
->hash_map
= env_vzalloc(
147 new_ctx
->hash_entries
* sizeof(*new_ctx
->hash_map
));
148 if (!new_ctx
->hash_map
) {
149 result
= -OCF_ERR_NO_MEM
;
152 for (i
= 0; i
< new_ctx
->hash_entries
; i
++)
153 new_ctx
->hash_map
[i
] = new_ctx
->rb_entries
;
155 new_ctx
->hash_locks
= env_vzalloc(
156 new_ctx
->hash_entries
* sizeof(*new_ctx
->hash_locks
));
157 if (!new_ctx
->hash_locks
) {
158 result
= -OCF_ERR_NO_MEM
;
162 for (i_locks
= 0; i_locks
< new_ctx
->hash_entries
; i_locks
++) {
163 if (env_rwsem_init(&new_ctx
->hash_locks
[i_locks
])) {
164 result
= -OCF_ERR_UNKNOWN
;
169 new_ctx
->ring_buffer
= env_vzalloc(
170 new_ctx
->rb_entries
* sizeof(*new_ctx
->ring_buffer
));
171 if (!new_ctx
->ring_buffer
) {
172 result
= -OCF_ERR_NO_MEM
;
175 for (i
= 0; i
< new_ctx
->rb_entries
; i
++) {
176 new_ctx
->ring_buffer
[i
].core_id
= OCF_CORE_ID_INVALID
;
177 new_ctx
->ring_buffer
[i
].valid
= true;
178 env_atomic_set(&new_ctx
->ring_buffer
[i
].counter
, 0);
181 result
= env_spinlock_init(&new_ctx
->rb_pointer_lock
);
185 new_ctx
->rb_pointer
= 0;
192 ENV_BUG_ON(env_rwsem_destroy(&new_ctx
->hash_locks
[i_locks
]));
193 env_vfree(new_ctx
->hash_locks
);
195 env_vfree(new_ctx
->hash_map
);
202 void nhit_hash_deinit(nhit_hash_t ctx
)
206 env_spinlock_destroy(&ctx
->rb_pointer_lock
);
207 for (i
= 0; i
< ctx
->hash_entries
; i
++)
208 ENV_BUG_ON(env_rwsem_destroy(&ctx
->hash_locks
[i
]));
210 env_vfree(ctx
->ring_buffer
);
211 env_vfree(ctx
->hash_locks
);
212 env_vfree(ctx
->hash_map
);
216 static ocf_cache_line_t
hash_function(ocf_core_id_t core_id
, uint64_t core_lba
,
219 if (core_id
== OCF_CORE_ID_INVALID
)
222 return (ocf_cache_line_t
) ((core_lba
* HASH_PRIME
+ core_id
) % limit
);
225 static ocf_cache_line_t
core_line_lookup(nhit_hash_t ctx
,
226 ocf_core_id_t core_id
, uint64_t core_lba
)
228 ocf_cache_line_t hash
= hash_function(core_id
, core_lba
,
230 ocf_cache_line_t needle
= ctx
->rb_entries
;
231 ocf_cache_line_t cur
;
233 for (cur
= ctx
->hash_map
[hash
]; cur
!= ctx
->rb_entries
;
234 cur
= ctx
->ring_buffer
[cur
].coll_next
) {
235 struct nhit_list_elem
*cur_elem
= &ctx
->ring_buffer
[cur
];
237 if (cur_elem
->core_lba
== core_lba
&&
238 cur_elem
->core_id
== core_id
) {
247 static inline bool get_rb_slot(nhit_hash_t ctx
, uint64_t *slot
)
251 OCF_CHECK_NULL(slot
);
253 env_spinlock_lock(&ctx
->rb_pointer_lock
);
255 *slot
= ctx
->rb_pointer
;
256 result
= ctx
->ring_buffer
[*slot
].valid
;
258 ctx
->ring_buffer
[*slot
].valid
= false;
260 ctx
->rb_pointer
= (*slot
+ 1) % ctx
->rb_entries
;
262 env_spinlock_unlock(&ctx
->rb_pointer_lock
);
267 static inline void commit_rb_slot(nhit_hash_t ctx
, uint64_t slot
)
269 env_spinlock_lock(&ctx
->rb_pointer_lock
);
271 ctx
->ring_buffer
[slot
].valid
= true;
273 env_spinlock_unlock(&ctx
->rb_pointer_lock
);
276 static void collision_remove(nhit_hash_t ctx
, uint64_t slot_id
)
278 struct nhit_list_elem
*slot
= &ctx
->ring_buffer
[slot_id
];
279 ocf_cache_line_t hash
= hash_function(slot
->core_id
, slot
->core_lba
,
282 if (slot
->core_id
== OCF_CORE_ID_INVALID
)
285 slot
->core_id
= OCF_CORE_ID_INVALID
;
287 if (slot
->coll_prev
!= ctx
->rb_entries
)
288 ctx
->ring_buffer
[slot
->coll_prev
].coll_next
= slot
->coll_next
;
290 if (slot
->coll_next
!= ctx
->rb_entries
)
291 ctx
->ring_buffer
[slot
->coll_next
].coll_prev
= slot
->coll_prev
;
293 if (ctx
->hash_map
[hash
] == slot_id
)
294 ctx
->hash_map
[hash
] = slot
->coll_next
;
297 static void collision_insert_new(nhit_hash_t ctx
,
298 uint64_t slot_id
, ocf_core_id_t core_id
,
301 ocf_cache_line_t hash
= hash_function(core_id
, core_lba
,
303 struct nhit_list_elem
*slot
= &ctx
->ring_buffer
[slot_id
];
305 slot
->core_id
= core_id
;
306 slot
->core_lba
= core_lba
;
307 slot
->coll_next
= ctx
->hash_map
[hash
];
308 slot
->coll_prev
= ctx
->rb_entries
;
309 env_atomic_set(&slot
->counter
, 1);
311 if (ctx
->hash_map
[hash
] != ctx
->rb_entries
)
312 ctx
->ring_buffer
[ctx
->hash_map
[hash
]].coll_prev
= slot_id
;
314 ctx
->hash_map
[hash
] = slot_id
;
317 static inline void write_lock_hashes(nhit_hash_t ctx
, ocf_core_id_t core_id1
,
318 uint64_t core_lba1
, ocf_core_id_t core_id2
, uint64_t core_lba2
)
320 ocf_cache_line_t hash1
= hash_function(core_id1
, core_lba1
,
322 ocf_cache_line_t hash2
= hash_function(core_id2
, core_lba2
,
324 ocf_cache_line_t lock_order
[2] = {
325 OCF_MIN(hash1
, hash2
),
326 OCF_MAX(hash1
, hash2
)};
328 if (lock_order
[0] != ctx
->hash_entries
)
329 env_rwsem_down_write(&ctx
->hash_locks
[lock_order
[0]]);
331 if ((lock_order
[1] != ctx
->hash_entries
) && (lock_order
[0] != lock_order
[1]))
332 env_rwsem_down_write(&ctx
->hash_locks
[lock_order
[1]]);
335 static inline void write_unlock_hashes(nhit_hash_t ctx
, ocf_core_id_t core_id1
,
336 uint64_t core_lba1
, ocf_core_id_t core_id2
, uint64_t core_lba2
)
338 ocf_cache_line_t hash1
= hash_function(core_id1
, core_lba1
,
340 ocf_cache_line_t hash2
= hash_function(core_id2
, core_lba2
,
343 if (hash1
!= ctx
->hash_entries
)
344 env_rwsem_up_write(&ctx
->hash_locks
[hash1
]);
346 if ((hash2
!= ctx
->hash_entries
) && (hash1
!= hash2
))
347 env_rwsem_up_write(&ctx
->hash_locks
[hash2
]);
350 void nhit_hash_insert(nhit_hash_t ctx
, ocf_core_id_t core_id
, uint64_t core_lba
)
353 struct nhit_list_elem
*slot
;
354 ocf_core_id_t slot_core_id
;
355 uint64_t slot_core_lba
;
357 if (!get_rb_slot(ctx
, &slot_id
))
360 slot
= &ctx
->ring_buffer
[slot_id
];
361 slot_core_id
= slot
->core_id
;
362 slot_core_lba
= slot
->core_lba
;
364 write_lock_hashes(ctx
, core_id
, core_lba
, slot_core_id
, slot_core_lba
);
366 collision_remove(ctx
, slot_id
);
367 collision_insert_new(ctx
, slot_id
, core_id
, core_lba
);
369 write_unlock_hashes(ctx
, core_id
, core_lba
, slot_core_id
, slot_core_lba
);
371 commit_rb_slot(ctx
, slot_id
);
374 bool nhit_hash_query(nhit_hash_t ctx
, ocf_core_id_t core_id
, uint64_t core_lba
,
377 ocf_cache_line_t hash
= hash_function(core_id
, core_lba
,
381 OCF_CHECK_NULL(counter
);
383 env_rwsem_down_read(&ctx
->hash_locks
[hash
]);
384 rb_idx
= core_line_lookup(ctx
, core_id
, core_lba
);
386 if (rb_idx
== ctx
->rb_entries
) {
387 env_rwsem_up_read(&ctx
->hash_locks
[hash
]);
391 *counter
= env_atomic_inc_return(&ctx
->ring_buffer
[rb_idx
].counter
);
393 env_rwsem_up_read(&ctx
->hash_locks
[hash
]);
398 void nhit_hash_set_occurences(nhit_hash_t ctx
, ocf_core_id_t core_id
,
399 uint64_t core_lba
, int32_t occurences
)
401 ocf_cache_line_t hash
= hash_function(core_id
, core_lba
,
405 env_rwsem_down_read(&ctx
->hash_locks
[hash
]);
406 rb_idx
= core_line_lookup(ctx
, core_id
, core_lba
);
408 if (rb_idx
== ctx
->rb_entries
) {
409 env_rwsem_up_read(&ctx
->hash_locks
[hash
]);
413 env_atomic_set(&ctx
->ring_buffer
[rb_idx
].counter
, occurences
);
415 env_rwsem_up_read(&ctx
->hash_locks
[hash
]);