Skip to content

Commit c356223

Browse files
davidhildenbrandhuth
authored andcommitted
hw/s390x/s390-skeys: lazy storage key enablement under TCG
Let's enable storage keys lazily under TCG, just as we do under KVM. Only fairly old Linux versions actually make use of storage keys, so it can be kind of wasteful to allocate quite some memory and track changes and references if nobody cares. We have to make sure to flush the TLB when enabling storage keys after the VM was already running: otherwise it might happen that we don't catch references or modifications afterwards. Add proper documentation to all callbacks. The kvm-unit-tests skey tests keeps on working with this change. Signed-off-by: David Hildenbrand <[email protected]> Reviewed-by: Thomas Huth <[email protected]> Message-Id: <[email protected]> Signed-off-by: Thomas Huth <[email protected]>
1 parent 5227b32 commit c356223

File tree

4 files changed

+131
-14
lines changed

4 files changed

+131
-14
lines changed

hw/s390x/s390-skeys.c

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -191,18 +191,45 @@ void qmp_dump_skeys(const char *filename, Error **errp)
191191
fclose(f);
192192
}
193193

194-
static void qemu_s390_skeys_init(Object *obj)
194+
static bool qemu_s390_skeys_are_enabled(S390SKeysState *ss)
195195
{
196-
QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(obj);
197-
MachineState *machine = MACHINE(qdev_get_machine());
196+
QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(ss);
198197

199-
skeys->key_count = machine->ram_size / TARGET_PAGE_SIZE;
200-
skeys->keydata = g_malloc0(skeys->key_count);
198+
/* Lockless check is sufficient. */
199+
return !!skeys->keydata;
201200
}
202201

203-
static bool qemu_s390_skeys_are_enabled(S390SKeysState *ss)
202+
static bool qemu_s390_enable_skeys(S390SKeysState *ss)
204203
{
205-
return true;
204+
QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(ss);
205+
static gsize initialized;
206+
207+
if (likely(skeys->keydata)) {
208+
return true;
209+
}
210+
211+
/*
212+
* TODO: Modern Linux doesn't use storage keys unless running KVM guests
213+
* that use storage keys. Therefore, we keep it simple for now.
214+
*
215+
* 1) We should initialize to "referenced+changed" for an initial
216+
* over-indication. Let's avoid touching megabytes of data for now and
217+
* assume that any sane user will issue a storage key instruction before
218+
* actually relying on this data.
219+
* 2) Relying on ram_size and allocating a big array is ugly. We should
220+
* allocate and manage storage key data per RAMBlock or optimally using
221+
* some sparse data structure.
222+
* 3) We only ever have a single S390SKeysState, so relying on
223+
* g_once_init_enter() is good enough.
224+
*/
225+
if (g_once_init_enter(&initialized)) {
226+
MachineState *machine = MACHINE(qdev_get_machine());
227+
228+
skeys->key_count = machine->ram_size / TARGET_PAGE_SIZE;
229+
skeys->keydata = g_malloc0(skeys->key_count);
230+
g_once_init_leave(&initialized, 1);
231+
}
232+
return false;
206233
}
207234

208235
static int qemu_s390_skeys_set(S390SKeysState *ss, uint64_t start_gfn,
@@ -212,9 +239,10 @@ static int qemu_s390_skeys_set(S390SKeysState *ss, uint64_t start_gfn,
212239
int i;
213240

214241
/* Check for uint64 overflow and access beyond end of key data */
215-
if (start_gfn + count > skeydev->key_count || start_gfn + count < count) {
216-
error_report("Error: Setting storage keys for page beyond the end "
217-
"of memory: gfn=%" PRIx64 " count=%" PRId64,
242+
if (unlikely(!skeydev->keydata || start_gfn + count > skeydev->key_count ||
243+
start_gfn + count < count)) {
244+
error_report("Error: Setting storage keys for pages with unallocated "
245+
"storage key memory: gfn=%" PRIx64 " count=%" PRId64,
218246
start_gfn, count);
219247
return -EINVAL;
220248
}
@@ -232,9 +260,10 @@ static int qemu_s390_skeys_get(S390SKeysState *ss, uint64_t start_gfn,
232260
int i;
233261

234262
/* Check for uint64 overflow and access beyond end of key data */
235-
if (start_gfn + count > skeydev->key_count || start_gfn + count < count) {
236-
error_report("Error: Getting storage keys for page beyond the end "
237-
"of memory: gfn=%" PRIx64 " count=%" PRId64,
263+
if (unlikely(!skeydev->keydata || start_gfn + count > skeydev->key_count ||
264+
start_gfn + count < count)) {
265+
error_report("Error: Getting storage keys for pages with unallocated "
266+
"storage key memory: gfn=%" PRIx64 " count=%" PRId64,
238267
start_gfn, count);
239268
return -EINVAL;
240269
}
@@ -251,6 +280,7 @@ static void qemu_s390_skeys_class_init(ObjectClass *oc, void *data)
251280
DeviceClass *dc = DEVICE_CLASS(oc);
252281

253282
skeyclass->skeys_are_enabled = qemu_s390_skeys_are_enabled;
283+
skeyclass->enable_skeys = qemu_s390_enable_skeys;
254284
skeyclass->get_skeys = qemu_s390_skeys_get;
255285
skeyclass->set_skeys = qemu_s390_skeys_set;
256286

@@ -261,7 +291,6 @@ static void qemu_s390_skeys_class_init(ObjectClass *oc, void *data)
261291
static const TypeInfo qemu_s390_skeys_info = {
262292
.name = TYPE_QEMU_S390_SKEYS,
263293
.parent = TYPE_S390_SKEYS,
264-
.instance_init = qemu_s390_skeys_init,
265294
.instance_size = sizeof(QEMUS390SKeysState),
266295
.class_init = qemu_s390_skeys_class_init,
267296
.class_size = sizeof(S390SKeysClass),
@@ -341,6 +370,14 @@ static int s390_storage_keys_load(QEMUFile *f, void *opaque, int version_id)
341370
S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss);
342371
int ret = 0;
343372

373+
/*
374+
* Make sure to lazy-enable if required to be done explicitly. No need to
375+
* flush any TLB as the VM is not running yet.
376+
*/
377+
if (skeyclass->enable_skeys) {
378+
skeyclass->enable_skeys(ss);
379+
}
380+
344381
while (!ret) {
345382
ram_addr_t addr;
346383
int flags;

include/hw/s390x/storage-keys.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,72 @@ struct S390SKeysState {
2828

2929
struct S390SKeysClass {
3030
DeviceClass parent_class;
31+
32+
/**
33+
* @skeys_are_enabled:
34+
*
35+
* Check whether storage keys are enabled. If not enabled, they were not
36+
* enabled lazily either by the guest via a storage key instruction or
37+
* by the host during migration.
38+
*
39+
* If disabled, everything not explicitly triggered by the guest,
40+
* such as outgoing migration or dirty/change tracking, should not touch
41+
* storage keys and should not lazily enable it.
42+
*
43+
* @ks: the #S390SKeysState
44+
*
45+
* Returns false if not enabled and true if enabled.
46+
*/
3147
bool (*skeys_are_enabled)(S390SKeysState *ks);
48+
49+
/**
50+
* @enable_skeys:
51+
*
52+
* Lazily enable storage keys. If this function is not implemented,
53+
* setting a storage key will lazily enable storage keys implicitly
54+
* instead. TCG guests have to make sure to flush the TLB of all CPUs
55+
* if storage keys were not enabled before this call.
56+
*
57+
* @ks: the #S390SKeysState
58+
*
59+
* Returns false if not enabled before this call, and true if already
60+
* enabled.
61+
*/
62+
bool (*enable_skeys)(S390SKeysState *ks);
63+
64+
/**
65+
* @get_skeys:
66+
*
67+
* Get storage keys for the given PFN range. This call will fail if
68+
* storage keys have not been lazily enabled yet.
69+
*
70+
* Callers have to validate that a GFN is valid before this call.
71+
*
72+
* @ks: the #S390SKeysState
73+
* @start_gfn: the start GFN to get storage keys for
74+
* @count: the number of storage keys to get
75+
* @keys: the byte array where storage keys will be stored to
76+
*
77+
* Returns 0 on success, returns an error if getting a storage key failed.
78+
*/
3279
int (*get_skeys)(S390SKeysState *ks, uint64_t start_gfn, uint64_t count,
3380
uint8_t *keys);
81+
/**
82+
* @set_skeys:
83+
*
84+
* Set storage keys for the given PFN range. This call will fail if
85+
* storage keys have not been lazily enabled yet and implicit
86+
* enablement is not supported.
87+
*
88+
* Callers have to validate that a GFN is valid before this call.
89+
*
90+
* @ks: the #S390SKeysState
91+
* @start_gfn: the start GFN to set storage keys for
92+
* @count: the number of storage keys to set
93+
* @keys: the byte array where storage keys will be read from
94+
*
95+
* Returns 0 on success, returns an error if setting a storage key failed.
96+
*/
3497
int (*set_skeys)(S390SKeysState *ks, uint64_t start_gfn, uint64_t count,
3598
uint8_t *keys);
3699
};

target/s390x/mmu_helper.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ static void mmu_handle_skey(target_ulong addr, int rw, int *flags)
313313
skeyclass = S390_SKEYS_GET_CLASS(ss);
314314
}
315315

316+
/*
317+
* Don't enable storage keys if they are still disabled, i.e., no actual
318+
* storage key instruction was issued yet.
319+
*/
320+
if (!skeyclass->skeys_are_enabled(ss)) {
321+
return;
322+
}
323+
316324
/*
317325
* Whenever we create a new TLB entry, we set the storage key reference
318326
* bit. In case we allow write accesses, we set the storage key change

target/s390x/tcg/mem_helper.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,6 +2186,9 @@ uint64_t HELPER(iske)(CPUS390XState *env, uint64_t r2)
21862186
if (unlikely(!ss)) {
21872187
ss = s390_get_skeys_device();
21882188
skeyclass = S390_SKEYS_GET_CLASS(ss);
2189+
if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) {
2190+
tlb_flush_all_cpus_synced(env_cpu(env));
2191+
}
21892192
}
21902193

21912194
rc = skeyclass->get_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
@@ -2213,6 +2216,9 @@ void HELPER(sske)(CPUS390XState *env, uint64_t r1, uint64_t r2)
22132216
if (unlikely(!ss)) {
22142217
ss = s390_get_skeys_device();
22152218
skeyclass = S390_SKEYS_GET_CLASS(ss);
2219+
if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) {
2220+
tlb_flush_all_cpus_synced(env_cpu(env));
2221+
}
22162222
}
22172223

22182224
key = r1 & 0xfe;
@@ -2244,6 +2250,9 @@ uint32_t HELPER(rrbe)(CPUS390XState *env, uint64_t r2)
22442250
if (unlikely(!ss)) {
22452251
ss = s390_get_skeys_device();
22462252
skeyclass = S390_SKEYS_GET_CLASS(ss);
2253+
if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) {
2254+
tlb_flush_all_cpus_synced(env_cpu(env));
2255+
}
22472256
}
22482257

22492258
rc = skeyclass->get_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);

0 commit comments

Comments
 (0)