The function adf_send_admin_tl_start() enables the telemetry (TL)
feature on a QAT device by sending the ICP_QAT_FW_TL_START message to
the firmware. This triggers the FW to start writing TL data to a DMA
buffer in memory and returns an array containing the number of
accelerators of each type (slices) supported by this HW.
The pointer to this array is stored in the adf_tl_hw_data data
structure called slice_cnt.
The array slice_cnt is then used in the function tl_print_dev_data()
to report in debugfs only statistics about the supported accelerators.
An incorrect value of the elements in slice_cnt might lead to an out
of bounds memory read.
At the moment, there isn't an implementation of FW that returns a wrong
value, but for robustness validate the slice count array returned by FW.
Fixes: 69e7649f7c
("crypto: qat - add support for device telemetry")
Signed-off-by: Lucas Segarra Fernandez <lucas.segarra.fernandez@intel.com>
Reviewed-by: Damian Muszynski <damian.muszynski@intel.com>
Reviewed-by: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
309 lines
7.3 KiB
C
309 lines
7.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2023 Intel Corporation. */
|
|
#define dev_fmt(fmt) "Telemetry: " fmt
|
|
|
|
#include <asm/errno.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "adf_admin.h"
|
|
#include "adf_accel_devices.h"
|
|
#include "adf_common_drv.h"
|
|
#include "adf_telemetry.h"
|
|
|
|
#define TL_IS_ZERO(input) ((input) == 0)
|
|
|
|
static bool is_tl_supported(struct adf_accel_dev *accel_dev)
|
|
{
|
|
u16 fw_caps = GET_HW_DATA(accel_dev)->fw_capabilities;
|
|
|
|
return fw_caps & TL_CAPABILITY_BIT;
|
|
}
|
|
|
|
static int validate_tl_data(struct adf_tl_hw_data *tl_data)
|
|
{
|
|
if (!tl_data->dev_counters ||
|
|
TL_IS_ZERO(tl_data->num_dev_counters) ||
|
|
!tl_data->sl_util_counters ||
|
|
!tl_data->sl_exec_counters ||
|
|
!tl_data->rp_counters ||
|
|
TL_IS_ZERO(tl_data->num_rp_counters))
|
|
return -EOPNOTSUPP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int validate_tl_slice_counters(struct icp_qat_fw_init_admin_slice_cnt *slice_count,
|
|
u8 max_slices_per_type)
|
|
{
|
|
u8 *sl_counter = (u8 *)slice_count;
|
|
int i;
|
|
|
|
for (i = 0; i < ADF_TL_SL_CNT_COUNT; i++) {
|
|
if (sl_counter[i] > max_slices_per_type)
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adf_tl_alloc_mem(struct adf_accel_dev *accel_dev)
|
|
{
|
|
struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
|
|
struct device *dev = &GET_DEV(accel_dev);
|
|
size_t regs_sz = tl_data->layout_sz;
|
|
struct adf_telemetry *telemetry;
|
|
int node = dev_to_node(dev);
|
|
void *tl_data_regs;
|
|
unsigned int i;
|
|
|
|
telemetry = kzalloc_node(sizeof(*telemetry), GFP_KERNEL, node);
|
|
if (!telemetry)
|
|
return -ENOMEM;
|
|
|
|
telemetry->rp_num_indexes = kmalloc_array(tl_data->max_rp,
|
|
sizeof(*telemetry->rp_num_indexes),
|
|
GFP_KERNEL);
|
|
if (!telemetry->rp_num_indexes)
|
|
goto err_free_tl;
|
|
|
|
telemetry->regs_hist_buff = kmalloc_array(tl_data->num_hbuff,
|
|
sizeof(*telemetry->regs_hist_buff),
|
|
GFP_KERNEL);
|
|
if (!telemetry->regs_hist_buff)
|
|
goto err_free_rp_indexes;
|
|
|
|
telemetry->regs_data = dma_alloc_coherent(dev, regs_sz,
|
|
&telemetry->regs_data_p,
|
|
GFP_KERNEL);
|
|
if (!telemetry->regs_data)
|
|
goto err_free_regs_hist_buff;
|
|
|
|
for (i = 0; i < tl_data->num_hbuff; i++) {
|
|
tl_data_regs = kzalloc_node(regs_sz, GFP_KERNEL, node);
|
|
if (!tl_data_regs)
|
|
goto err_free_dma;
|
|
|
|
telemetry->regs_hist_buff[i] = tl_data_regs;
|
|
}
|
|
|
|
accel_dev->telemetry = telemetry;
|
|
|
|
return 0;
|
|
|
|
err_free_dma:
|
|
dma_free_coherent(dev, regs_sz, telemetry->regs_data,
|
|
telemetry->regs_data_p);
|
|
|
|
while (i--)
|
|
kfree(telemetry->regs_hist_buff[i]);
|
|
|
|
err_free_regs_hist_buff:
|
|
kfree(telemetry->regs_hist_buff);
|
|
err_free_rp_indexes:
|
|
kfree(telemetry->rp_num_indexes);
|
|
err_free_tl:
|
|
kfree(telemetry);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void adf_tl_free_mem(struct adf_accel_dev *accel_dev)
|
|
{
|
|
struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
|
|
struct adf_telemetry *telemetry = accel_dev->telemetry;
|
|
struct device *dev = &GET_DEV(accel_dev);
|
|
size_t regs_sz = tl_data->layout_sz;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < tl_data->num_hbuff; i++)
|
|
kfree(telemetry->regs_hist_buff[i]);
|
|
|
|
dma_free_coherent(dev, regs_sz, telemetry->regs_data,
|
|
telemetry->regs_data_p);
|
|
|
|
kfree(telemetry->regs_hist_buff);
|
|
kfree(telemetry->rp_num_indexes);
|
|
kfree(telemetry);
|
|
accel_dev->telemetry = NULL;
|
|
}
|
|
|
|
static unsigned long get_next_timeout(void)
|
|
{
|
|
return msecs_to_jiffies(ADF_TL_TIMER_INT_MS);
|
|
}
|
|
|
|
static void snapshot_regs(struct adf_telemetry *telemetry, size_t size)
|
|
{
|
|
void *dst = telemetry->regs_hist_buff[telemetry->hb_num];
|
|
void *src = telemetry->regs_data;
|
|
|
|
memcpy(dst, src, size);
|
|
}
|
|
|
|
static void tl_work_handler(struct work_struct *work)
|
|
{
|
|
struct delayed_work *delayed_work;
|
|
struct adf_telemetry *telemetry;
|
|
struct adf_tl_hw_data *tl_data;
|
|
u32 msg_cnt, old_msg_cnt;
|
|
size_t layout_sz;
|
|
u32 *regs_data;
|
|
size_t id;
|
|
|
|
delayed_work = to_delayed_work(work);
|
|
telemetry = container_of(delayed_work, struct adf_telemetry, work_ctx);
|
|
tl_data = &GET_TL_DATA(telemetry->accel_dev);
|
|
regs_data = telemetry->regs_data;
|
|
|
|
id = tl_data->msg_cnt_off / sizeof(*regs_data);
|
|
layout_sz = tl_data->layout_sz;
|
|
|
|
if (!atomic_read(&telemetry->state)) {
|
|
cancel_delayed_work_sync(&telemetry->work_ctx);
|
|
return;
|
|
}
|
|
|
|
msg_cnt = regs_data[id];
|
|
old_msg_cnt = msg_cnt;
|
|
if (msg_cnt == telemetry->msg_cnt)
|
|
goto out;
|
|
|
|
mutex_lock(&telemetry->regs_hist_lock);
|
|
|
|
snapshot_regs(telemetry, layout_sz);
|
|
|
|
/* Check if data changed while updating it */
|
|
msg_cnt = regs_data[id];
|
|
if (old_msg_cnt != msg_cnt)
|
|
snapshot_regs(telemetry, layout_sz);
|
|
|
|
telemetry->msg_cnt = msg_cnt;
|
|
telemetry->hb_num++;
|
|
telemetry->hb_num %= telemetry->hbuffs;
|
|
|
|
mutex_unlock(&telemetry->regs_hist_lock);
|
|
|
|
out:
|
|
adf_misc_wq_queue_delayed_work(&telemetry->work_ctx, get_next_timeout());
|
|
}
|
|
|
|
int adf_tl_halt(struct adf_accel_dev *accel_dev)
|
|
{
|
|
struct adf_telemetry *telemetry = accel_dev->telemetry;
|
|
struct device *dev = &GET_DEV(accel_dev);
|
|
int ret;
|
|
|
|
cancel_delayed_work_sync(&telemetry->work_ctx);
|
|
atomic_set(&telemetry->state, 0);
|
|
|
|
ret = adf_send_admin_tl_stop(accel_dev);
|
|
if (ret)
|
|
dev_err(dev, "failed to stop telemetry\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
int adf_tl_run(struct adf_accel_dev *accel_dev, int state)
|
|
{
|
|
struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
|
|
struct adf_telemetry *telemetry = accel_dev->telemetry;
|
|
struct device *dev = &GET_DEV(accel_dev);
|
|
size_t layout_sz = tl_data->layout_sz;
|
|
int ret;
|
|
|
|
ret = adf_send_admin_tl_start(accel_dev, telemetry->regs_data_p,
|
|
layout_sz, telemetry->rp_num_indexes,
|
|
&telemetry->slice_cnt);
|
|
if (ret) {
|
|
dev_err(dev, "failed to start telemetry\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = validate_tl_slice_counters(&telemetry->slice_cnt, tl_data->max_sl_cnt);
|
|
if (ret) {
|
|
dev_err(dev, "invalid value returned by FW\n");
|
|
adf_send_admin_tl_stop(accel_dev);
|
|
return ret;
|
|
}
|
|
|
|
telemetry->hbuffs = state;
|
|
atomic_set(&telemetry->state, state);
|
|
|
|
adf_misc_wq_queue_delayed_work(&telemetry->work_ctx, get_next_timeout());
|
|
|
|
return 0;
|
|
}
|
|
|
|
int adf_tl_init(struct adf_accel_dev *accel_dev)
|
|
{
|
|
struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
|
|
u8 max_rp = GET_TL_DATA(accel_dev).max_rp;
|
|
struct device *dev = &GET_DEV(accel_dev);
|
|
struct adf_telemetry *telemetry;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = validate_tl_data(tl_data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = adf_tl_alloc_mem(accel_dev);
|
|
if (ret) {
|
|
dev_err(dev, "failed to initialize: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
telemetry = accel_dev->telemetry;
|
|
telemetry->accel_dev = accel_dev;
|
|
|
|
mutex_init(&telemetry->wr_lock);
|
|
mutex_init(&telemetry->regs_hist_lock);
|
|
INIT_DELAYED_WORK(&telemetry->work_ctx, tl_work_handler);
|
|
|
|
for (i = 0; i < max_rp; i++)
|
|
telemetry->rp_num_indexes[i] = ADF_TL_RP_REGS_DISABLED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int adf_tl_start(struct adf_accel_dev *accel_dev)
|
|
{
|
|
struct device *dev = &GET_DEV(accel_dev);
|
|
|
|
if (!accel_dev->telemetry)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (!is_tl_supported(accel_dev)) {
|
|
dev_info(dev, "feature not supported by FW\n");
|
|
adf_tl_free_mem(accel_dev);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void adf_tl_stop(struct adf_accel_dev *accel_dev)
|
|
{
|
|
if (!accel_dev->telemetry)
|
|
return;
|
|
|
|
if (atomic_read(&accel_dev->telemetry->state))
|
|
adf_tl_halt(accel_dev);
|
|
}
|
|
|
|
void adf_tl_shutdown(struct adf_accel_dev *accel_dev)
|
|
{
|
|
if (!accel_dev->telemetry)
|
|
return;
|
|
|
|
adf_tl_free_mem(accel_dev);
|
|
}
|