@@ -123,7 +123,7 @@ struct virtio_mem {
123
123
*
124
124
* When this lock is held the pointers can't change, ONLINE and
125
125
* OFFLINE blocks can't change the state and no subblocks will get
126
- * plugged.
126
+ * plugged/unplugged .
127
127
*/
128
128
struct mutex hotplug_mutex ;
129
129
bool hotplug_active ;
@@ -280,6 +280,12 @@ static int virtio_mem_mb_state_prepare_next_mb(struct virtio_mem *vm)
280
280
_mb_id++) \
281
281
if (virtio_mem_mb_get_state(_vm, _mb_id) == _state)
282
282
283
+ #define virtio_mem_for_each_mb_state_rev (_vm , _mb_id , _state ) \
284
+ for (_mb_id = _vm->next_mb_id - 1; \
285
+ _mb_id >= _vm->first_mb_id && _vm->nb_mb_state[_state]; \
286
+ _mb_id--) \
287
+ if (virtio_mem_mb_get_state(_vm, _mb_id) == _state)
288
+
283
289
/*
284
290
* Mark all selected subblocks plugged.
285
291
*
@@ -325,6 +331,19 @@ static bool virtio_mem_mb_test_sb_plugged(struct virtio_mem *vm,
325
331
bit + count ;
326
332
}
327
333
334
+ /*
335
+ * Test if all selected subblocks are unplugged.
336
+ */
337
+ static bool virtio_mem_mb_test_sb_unplugged (struct virtio_mem * vm ,
338
+ unsigned long mb_id , int sb_id ,
339
+ int count )
340
+ {
341
+ const int bit = (mb_id - vm -> first_mb_id ) * vm -> nb_sb_per_mb + sb_id ;
342
+
343
+ /* TODO: Helper similar to bitmap_set() */
344
+ return find_next_bit (vm -> sb_bitmap , bit + count , bit ) >= bit + count ;
345
+ }
346
+
328
347
/*
329
348
* Find the first plugged subblock. Returns vm->nb_sb_per_mb in case there is
330
349
* none.
@@ -513,6 +532,9 @@ static void virtio_mem_notify_offline(struct virtio_mem *vm,
513
532
BUG ();
514
533
break ;
515
534
}
535
+
536
+ /* trigger the workqueue, maybe we can now unplug memory. */
537
+ virtio_mem_retry (vm );
516
538
}
517
539
518
540
static void virtio_mem_notify_online (struct virtio_mem * vm , unsigned long mb_id ,
@@ -1122,6 +1144,94 @@ static int virtio_mem_plug_request(struct virtio_mem *vm, uint64_t diff)
1122
1144
return rc ;
1123
1145
}
1124
1146
1147
+ /*
1148
+ * Unplug the desired number of plugged subblocks of an offline memory block.
1149
+ * Will fail if any subblock cannot get unplugged (instead of skipping it).
1150
+ *
1151
+ * Will modify the state of the memory block. Might temporarily drop the
1152
+ * hotplug_mutex.
1153
+ *
1154
+ * Note: Can fail after some subblocks were successfully unplugged.
1155
+ */
1156
+ static int virtio_mem_mb_unplug_any_sb_offline (struct virtio_mem * vm ,
1157
+ unsigned long mb_id ,
1158
+ uint64_t * nb_sb )
1159
+ {
1160
+ int rc ;
1161
+
1162
+ rc = virtio_mem_mb_unplug_any_sb (vm , mb_id , nb_sb );
1163
+
1164
+ /* some subblocks might have been unplugged even on failure */
1165
+ if (!virtio_mem_mb_test_sb_plugged (vm , mb_id , 0 , vm -> nb_sb_per_mb ))
1166
+ virtio_mem_mb_set_state (vm , mb_id ,
1167
+ VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL );
1168
+ if (rc )
1169
+ return rc ;
1170
+
1171
+ if (virtio_mem_mb_test_sb_unplugged (vm , mb_id , 0 , vm -> nb_sb_per_mb )) {
1172
+ /*
1173
+ * Remove the block from Linux - this should never fail.
1174
+ * Hinder the block from getting onlined by marking it
1175
+ * unplugged. Temporarily drop the mutex, so
1176
+ * any pending GOING_ONLINE requests can be serviced/rejected.
1177
+ */
1178
+ virtio_mem_mb_set_state (vm , mb_id ,
1179
+ VIRTIO_MEM_MB_STATE_UNUSED );
1180
+
1181
+ mutex_unlock (& vm -> hotplug_mutex );
1182
+ rc = virtio_mem_mb_remove (vm , mb_id );
1183
+ BUG_ON (rc );
1184
+ mutex_lock (& vm -> hotplug_mutex );
1185
+ }
1186
+ return 0 ;
1187
+ }
1188
+
1189
+ /*
1190
+ * Try to unplug the requested amount of memory.
1191
+ */
1192
+ static int virtio_mem_unplug_request (struct virtio_mem * vm , uint64_t diff )
1193
+ {
1194
+ uint64_t nb_sb = diff / vm -> subblock_size ;
1195
+ unsigned long mb_id ;
1196
+ int rc ;
1197
+
1198
+ if (!nb_sb )
1199
+ return 0 ;
1200
+
1201
+ /*
1202
+ * We'll drop the mutex a couple of times when it is safe to do so.
1203
+ * This might result in some blocks switching the state (online/offline)
1204
+ * and we could miss them in this run - we will retry again later.
1205
+ */
1206
+ mutex_lock (& vm -> hotplug_mutex );
1207
+
1208
+ /* Try to unplug subblocks of partially plugged offline blocks. */
1209
+ virtio_mem_for_each_mb_state_rev (vm , mb_id ,
1210
+ VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL ) {
1211
+ rc = virtio_mem_mb_unplug_any_sb_offline (vm , mb_id ,
1212
+ & nb_sb );
1213
+ if (rc || !nb_sb )
1214
+ goto out_unlock ;
1215
+ cond_resched ();
1216
+ }
1217
+
1218
+ /* Try to unplug subblocks of plugged offline blocks. */
1219
+ virtio_mem_for_each_mb_state_rev (vm , mb_id ,
1220
+ VIRTIO_MEM_MB_STATE_OFFLINE ) {
1221
+ rc = virtio_mem_mb_unplug_any_sb_offline (vm , mb_id ,
1222
+ & nb_sb );
1223
+ if (rc || !nb_sb )
1224
+ goto out_unlock ;
1225
+ cond_resched ();
1226
+ }
1227
+
1228
+ mutex_unlock (& vm -> hotplug_mutex );
1229
+ return 0 ;
1230
+ out_unlock :
1231
+ mutex_unlock (& vm -> hotplug_mutex );
1232
+ return rc ;
1233
+ }
1234
+
1125
1235
/*
1126
1236
* Try to unplug all blocks that couldn't be unplugged before, for example,
1127
1237
* because the hypervisor was busy.
@@ -1204,8 +1314,10 @@ static void virtio_mem_run_wq(struct work_struct *work)
1204
1314
if (vm -> requested_size > vm -> plugged_size ) {
1205
1315
diff = vm -> requested_size - vm -> plugged_size ;
1206
1316
rc = virtio_mem_plug_request (vm , diff );
1317
+ } else {
1318
+ diff = vm -> plugged_size - vm -> requested_size ;
1319
+ rc = virtio_mem_unplug_request (vm , diff );
1207
1320
}
1208
- /* TODO: try to unplug memory */
1209
1321
}
1210
1322
1211
1323
switch (rc ) {
0 commit comments