s390/pai_crypto: dynamically allocate percpu pai crypto map data structure

Struct paicrypt_map is a data structure and is statically defined
for each possible CPU. Rework this and replace it by dynamically
allocated data structures created when a perf_event_open() system call
is invoked.

It is replaced by an array of pointers to all possible CPUs and
reference counting. The array of pointers is allocated when the first
event is created. For each online CPU an event is installed on, a struct
paicrypt_map is allocated and a pointer to struct cpu_cf_events is
stored in the array:

                        CPU   0   1   2   3    ...  N
                              +---+---+---+---+---+---+
     paicrypt_root::mapptr--> | * |   |   |   |...|   |
                              +-|-+---+---+---+---+---+
                                |
                                |
                               \|/
                        +--------------+
                        | paicrypt_map |
                        +--------------+

With this approach the large data structure is only allocated when
an event is actually installed and used.
Also implement proper reference counting for allocation and removal.

PAI crypto counter events can not be created when a CPU hot plug
add is processed. This means a CPU hot plug add does not get
the necessary PAI event to record PAI cryptography counter increments
on the newly added CPU. There is no possibility to notify user space
of a new CPU and the necessary event infrastructure assoiciated with
the file descriptor returned by perf_event_open() system call.
However system call perf_event_open() can use the newly added CPU
when issued after the CPU hot plug add.

Kernel CPU hot plug remove deletes the CPU and stops the PAI counters on
that CPU. When the process closes the file descriptor associated
with that event, the event's destroy() function removes any
allocated data structures and adjusts the reference counts.

Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
Acked-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Acked-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
This commit is contained in:
Thomas Richter 2023-08-21 16:49:05 +02:00 committed by Vasily Gorbik
parent 4f62c6e301
commit 60f8f641f3

View File

@ -40,7 +40,43 @@ struct paicrypt_map {
struct perf_event *event; /* Perf event for sampling */
};
static DEFINE_PER_CPU(struct paicrypt_map, paicrypt_map);
struct paicrypt_mapptr {
struct paicrypt_map *mapptr;
};
static struct paicrypt_root { /* Anchor to per CPU data */
refcount_t refcnt; /* Overall active events */
struct paicrypt_mapptr __percpu *mapptr;
} paicrypt_root;
/* Free per CPU data when the last event is removed. */
static void paicrypt_root_free(void)
{
if (refcount_dec_and_test(&paicrypt_root.refcnt)) {
free_percpu(paicrypt_root.mapptr);
paicrypt_root.mapptr = NULL;
}
debug_sprintf_event(cfm_dbg, 5, "%s root.refcount %d\n", __func__,
refcount_read(&paicrypt_root.refcnt));
}
/*
* On initialization of first event also allocate per CPU data dynamically.
* Start with an array of pointers, the array size is the maximum number of
* CPUs possible, which might be larger than the number of CPUs currently
* online.
*/
static int paicrypt_root_alloc(void)
{
if (!refcount_inc_not_zero(&paicrypt_root.refcnt)) {
/* The memory is already zeroed. */
paicrypt_root.mapptr = alloc_percpu(struct paicrypt_mapptr);
if (!paicrypt_root.mapptr)
return -ENOMEM;
refcount_set(&paicrypt_root.refcnt, 1);
}
return 0;
}
/* Release the PMU if event is the last perf event */
static DEFINE_MUTEX(pai_reserve_mutex);
@ -50,7 +86,9 @@ static DEFINE_MUTEX(pai_reserve_mutex);
*/
static void paicrypt_event_destroy(struct perf_event *event)
{
struct paicrypt_map *cpump = per_cpu_ptr(&paicrypt_map, event->cpu);
struct paicrypt_mapptr *mp = per_cpu_ptr(paicrypt_root.mapptr,
event->cpu);
struct paicrypt_map *cpump = mp->mapptr;
cpump->event = NULL;
static_branch_dec(&pai_key);
@ -65,11 +103,11 @@ static void paicrypt_event_destroy(struct perf_event *event)
__func__, (unsigned long)cpump->page,
cpump->save);
free_page((unsigned long)cpump->page);
cpump->page = NULL;
kvfree(cpump->save);
cpump->save = NULL;
cpump->mode = PAI_MODE_NONE;
kfree(cpump);
mp->mapptr = NULL;
}
paicrypt_root_free();
mutex_unlock(&pai_reserve_mutex);
}
@ -85,7 +123,8 @@ static u64 paicrypt_getctr(struct paicrypt_map *cpump, int nr, bool kernel)
*/
static u64 paicrypt_getdata(struct perf_event *event, bool kernel)
{
struct paicrypt_map *cpump = this_cpu_ptr(&paicrypt_map);
struct paicrypt_mapptr *mp = this_cpu_ptr(paicrypt_root.mapptr);
struct paicrypt_map *cpump = mp->mapptr;
u64 sum = 0;
int i;
@ -131,11 +170,31 @@ static u64 paicrypt_getall(struct perf_event *event)
*
* Allocate the memory for the event.
*/
static int paicrypt_busy(struct perf_event_attr *a, struct paicrypt_map *cpump)
static struct paicrypt_map *paicrypt_busy(struct perf_event *event)
{
int rc = 0;
struct perf_event_attr *a = &event->attr;
struct paicrypt_map *cpump = NULL;
struct paicrypt_mapptr *mp;
int rc;
mutex_lock(&pai_reserve_mutex);
/* Allocate root node */
rc = paicrypt_root_alloc();
if (rc)
goto unlock;
/* Allocate node for this event */
mp = per_cpu_ptr(paicrypt_root.mapptr, event->cpu);
cpump = mp->mapptr;
if (!cpump) { /* Paicrypt_map allocated? */
cpump = kzalloc(sizeof(*cpump), GFP_KERNEL);
if (!cpump) {
rc = -ENOMEM;
goto free_root;
}
}
if (a->sample_period) { /* Sampling requested */
if (cpump->mode != PAI_MODE_NONE)
rc = -EBUSY; /* ... sampling/counting active */
@ -143,8 +202,15 @@ static int paicrypt_busy(struct perf_event_attr *a, struct paicrypt_map *cpump)
if (cpump->mode == PAI_MODE_SAMPLING)
rc = -EBUSY; /* ... and sampling active */
}
/*
* This error case triggers when there is a conflict:
* Either sampling requested and counting already active, or visa
* versa. Therefore the struct paicrypto_map for this CPU is
* needed or the error could not have occurred. Only adjust root
* node refcount.
*/
if (rc)
goto unlock;
goto free_root;
/* Allocate memory for counter page and counter extraction.
* Only the first counting event has to allocate a page.
@ -157,30 +223,36 @@ static int paicrypt_busy(struct perf_event_attr *a, struct paicrypt_map *cpump)
rc = -ENOMEM;
cpump->page = (unsigned long *)get_zeroed_page(GFP_KERNEL);
if (!cpump->page)
goto unlock;
goto free_paicrypt_map;
cpump->save = kvmalloc_array(paicrypt_cnt + 1,
sizeof(struct pai_userdata), GFP_KERNEL);
if (!cpump->save) {
free_page((unsigned long)cpump->page);
cpump->page = NULL;
goto unlock;
goto free_paicrypt_map;
}
/* Set mode and reference count */
rc = 0;
refcount_set(&cpump->refcnt, 1);
unlock:
/* If rc is non-zero, do not set mode and reference count */
if (!rc) {
cpump->mode = a->sample_period ? PAI_MODE_SAMPLING
: PAI_MODE_COUNTING;
}
cpump->mode = a->sample_period ? PAI_MODE_SAMPLING : PAI_MODE_COUNTING;
mp->mapptr = cpump;
debug_sprintf_event(cfm_dbg, 5, "%s sample_period %#llx users %d"
" mode %d refcnt %u page %#lx save %p rc %d\n",
__func__, a->sample_period, cpump->active_events,
cpump->mode, refcount_read(&cpump->refcnt),
(unsigned long)cpump->page, cpump->save, rc);
goto unlock;
free_paicrypt_map:
kfree(cpump);
mp->mapptr = NULL;
free_root:
paicrypt_root_free();
unlock:
mutex_unlock(&pai_reserve_mutex);
return rc;
return rc ? ERR_PTR(rc) : cpump;
}
/* Might be called on different CPU than the one the event is intended for. */
@ -188,7 +260,6 @@ static int paicrypt_event_init(struct perf_event *event)
{
struct perf_event_attr *a = &event->attr;
struct paicrypt_map *cpump;
int rc;
/* PAI crypto PMU registered as PERF_TYPE_RAW, check event type */
if (a->type != PERF_TYPE_RAW && event->pmu->type != a->type)
@ -204,10 +275,9 @@ static int paicrypt_event_init(struct perf_event *event)
if (a->sample_period && a->config != PAI_CRYPTO_BASE)
return -EINVAL;
cpump = per_cpu_ptr(&paicrypt_map, event->cpu);
rc = paicrypt_busy(a, cpump);
if (rc)
return rc;
cpump = paicrypt_busy(event);
if (IS_ERR(cpump))
return PTR_ERR(cpump);
/* Event initialization sets last_tag to 0. When later on the events
* are deleted and re-added, do not reset the event count value to zero.
@ -259,7 +329,8 @@ static void paicrypt_start(struct perf_event *event, int flags)
static int paicrypt_add(struct perf_event *event, int flags)
{
struct paicrypt_map *cpump = this_cpu_ptr(&paicrypt_map);
struct paicrypt_mapptr *mp = this_cpu_ptr(paicrypt_root.mapptr);
struct paicrypt_map *cpump = mp->mapptr;
unsigned long ccd;
if (++cpump->active_events == 1) {
@ -286,7 +357,8 @@ static void paicrypt_stop(struct perf_event *event, int flags)
static void paicrypt_del(struct perf_event *event, int flags)
{
struct paicrypt_map *cpump = this_cpu_ptr(&paicrypt_map);
struct paicrypt_mapptr *mp = this_cpu_ptr(paicrypt_root.mapptr);
struct paicrypt_map *cpump = mp->mapptr;
if (event->attr.sample_period)
perf_sched_cb_dec(event->pmu);
@ -328,7 +400,8 @@ static size_t paicrypt_copy(struct pai_userdata *userdata,
static int paicrypt_push_sample(void)
{
struct paicrypt_map *cpump = this_cpu_ptr(&paicrypt_map);
struct paicrypt_mapptr *mp = this_cpu_ptr(paicrypt_root.mapptr);
struct paicrypt_map *cpump = mp->mapptr;
struct perf_event *event = cpump->event;
struct perf_sample_data data;
struct perf_raw_record raw;