linux_dsm_epyc7002/drivers/dma/idxd/device.c
Dave Jiang a1fcaf07ec dmaengine: idxd: reflect shadow copy of traffic class programming
The traffic class are set to -1 at initialization until the user programs
them. If the user choose not to, the driver will program appropriate
defaults. The driver also needs to update the shadowed copies of the values
after doing the programming.

Fixes: c52ca47823 ("dmaengine: idxd: add configuration component of driver")
Reported-by: Yixin Zhang <yixin.zhang@intel.com>
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Link: https://lore.kernel.org/r/158386263076.10898.4586509576813094559.stgit@djiang5-desk3.ch.intel.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
2020-03-11 15:08:52 +05:30

694 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2019 Intel Corporation. All rights rsvd. */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/dmaengine.h>
#include <uapi/linux/idxd.h>
#include "../dmaengine.h"
#include "idxd.h"
#include "registers.h"
static int idxd_cmd_wait(struct idxd_device *idxd, u32 *status, int timeout);
static int idxd_cmd_send(struct idxd_device *idxd, int cmd_code, u32 operand);
/* Interrupt control bits */
int idxd_mask_msix_vector(struct idxd_device *idxd, int vec_id)
{
struct pci_dev *pdev = idxd->pdev;
int msixcnt = pci_msix_vec_count(pdev);
union msix_perm perm;
u32 offset;
if (vec_id < 0 || vec_id >= msixcnt)
return -EINVAL;
offset = idxd->msix_perm_offset + vec_id * 8;
perm.bits = ioread32(idxd->reg_base + offset);
perm.ignore = 1;
iowrite32(perm.bits, idxd->reg_base + offset);
return 0;
}
void idxd_mask_msix_vectors(struct idxd_device *idxd)
{
struct pci_dev *pdev = idxd->pdev;
int msixcnt = pci_msix_vec_count(pdev);
int i, rc;
for (i = 0; i < msixcnt; i++) {
rc = idxd_mask_msix_vector(idxd, i);
if (rc < 0)
dev_warn(&pdev->dev,
"Failed disabling msix vec %d\n", i);
}
}
int idxd_unmask_msix_vector(struct idxd_device *idxd, int vec_id)
{
struct pci_dev *pdev = idxd->pdev;
int msixcnt = pci_msix_vec_count(pdev);
union msix_perm perm;
u32 offset;
if (vec_id < 0 || vec_id >= msixcnt)
return -EINVAL;
offset = idxd->msix_perm_offset + vec_id * 8;
perm.bits = ioread32(idxd->reg_base + offset);
perm.ignore = 0;
iowrite32(perm.bits, idxd->reg_base + offset);
return 0;
}
void idxd_unmask_error_interrupts(struct idxd_device *idxd)
{
union genctrl_reg genctrl;
genctrl.bits = ioread32(idxd->reg_base + IDXD_GENCTRL_OFFSET);
genctrl.softerr_int_en = 1;
iowrite32(genctrl.bits, idxd->reg_base + IDXD_GENCTRL_OFFSET);
}
void idxd_mask_error_interrupts(struct idxd_device *idxd)
{
union genctrl_reg genctrl;
genctrl.bits = ioread32(idxd->reg_base + IDXD_GENCTRL_OFFSET);
genctrl.softerr_int_en = 0;
iowrite32(genctrl.bits, idxd->reg_base + IDXD_GENCTRL_OFFSET);
}
static void free_hw_descs(struct idxd_wq *wq)
{
int i;
for (i = 0; i < wq->num_descs; i++)
kfree(wq->hw_descs[i]);
kfree(wq->hw_descs);
}
static int alloc_hw_descs(struct idxd_wq *wq, int num)
{
struct device *dev = &wq->idxd->pdev->dev;
int i;
int node = dev_to_node(dev);
wq->hw_descs = kcalloc_node(num, sizeof(struct dsa_hw_desc *),
GFP_KERNEL, node);
if (!wq->hw_descs)
return -ENOMEM;
for (i = 0; i < num; i++) {
wq->hw_descs[i] = kzalloc_node(sizeof(*wq->hw_descs[i]),
GFP_KERNEL, node);
if (!wq->hw_descs[i]) {
free_hw_descs(wq);
return -ENOMEM;
}
}
return 0;
}
static void free_descs(struct idxd_wq *wq)
{
int i;
for (i = 0; i < wq->num_descs; i++)
kfree(wq->descs[i]);
kfree(wq->descs);
}
static int alloc_descs(struct idxd_wq *wq, int num)
{
struct device *dev = &wq->idxd->pdev->dev;
int i;
int node = dev_to_node(dev);
wq->descs = kcalloc_node(num, sizeof(struct idxd_desc *),
GFP_KERNEL, node);
if (!wq->descs)
return -ENOMEM;
for (i = 0; i < num; i++) {
wq->descs[i] = kzalloc_node(sizeof(*wq->descs[i]),
GFP_KERNEL, node);
if (!wq->descs[i]) {
free_descs(wq);
return -ENOMEM;
}
}
return 0;
}
/* WQ control bits */
int idxd_wq_alloc_resources(struct idxd_wq *wq)
{
struct idxd_device *idxd = wq->idxd;
struct idxd_group *group = wq->group;
struct device *dev = &idxd->pdev->dev;
int rc, num_descs, i;
if (wq->type != IDXD_WQT_KERNEL)
return 0;
num_descs = wq->size +
idxd->hw.gen_cap.max_descs_per_engine * group->num_engines;
wq->num_descs = num_descs;
rc = alloc_hw_descs(wq, num_descs);
if (rc < 0)
return rc;
wq->compls_size = num_descs * sizeof(struct dsa_completion_record);
wq->compls = dma_alloc_coherent(dev, wq->compls_size,
&wq->compls_addr, GFP_KERNEL);
if (!wq->compls) {
rc = -ENOMEM;
goto fail_alloc_compls;
}
rc = alloc_descs(wq, num_descs);
if (rc < 0)
goto fail_alloc_descs;
rc = sbitmap_init_node(&wq->sbmap, num_descs, -1, GFP_KERNEL,
dev_to_node(dev));
if (rc < 0)
goto fail_sbitmap_init;
for (i = 0; i < num_descs; i++) {
struct idxd_desc *desc = wq->descs[i];
desc->hw = wq->hw_descs[i];
desc->completion = &wq->compls[i];
desc->compl_dma = wq->compls_addr +
sizeof(struct dsa_completion_record) * i;
desc->id = i;
desc->wq = wq;
dma_async_tx_descriptor_init(&desc->txd, &wq->dma_chan);
desc->txd.tx_submit = idxd_dma_tx_submit;
}
return 0;
fail_sbitmap_init:
free_descs(wq);
fail_alloc_descs:
dma_free_coherent(dev, wq->compls_size, wq->compls, wq->compls_addr);
fail_alloc_compls:
free_hw_descs(wq);
return rc;
}
void idxd_wq_free_resources(struct idxd_wq *wq)
{
struct device *dev = &wq->idxd->pdev->dev;
if (wq->type != IDXD_WQT_KERNEL)
return;
free_hw_descs(wq);
free_descs(wq);
dma_free_coherent(dev, wq->compls_size, wq->compls, wq->compls_addr);
sbitmap_free(&wq->sbmap);
}
int idxd_wq_enable(struct idxd_wq *wq)
{
struct idxd_device *idxd = wq->idxd;
struct device *dev = &idxd->pdev->dev;
u32 status;
int rc;
lockdep_assert_held(&idxd->dev_lock);
if (wq->state == IDXD_WQ_ENABLED) {
dev_dbg(dev, "WQ %d already enabled\n", wq->id);
return -ENXIO;
}
rc = idxd_cmd_send(idxd, IDXD_CMD_ENABLE_WQ, wq->id);
if (rc < 0)
return rc;
rc = idxd_cmd_wait(idxd, &status, IDXD_REG_TIMEOUT);
if (rc < 0)
return rc;
if (status != IDXD_CMDSTS_SUCCESS &&
status != IDXD_CMDSTS_ERR_WQ_ENABLED) {
dev_dbg(dev, "WQ enable failed: %#x\n", status);
return -ENXIO;
}
wq->state = IDXD_WQ_ENABLED;
dev_dbg(dev, "WQ %d enabled\n", wq->id);
return 0;
}
int idxd_wq_disable(struct idxd_wq *wq)
{
struct idxd_device *idxd = wq->idxd;
struct device *dev = &idxd->pdev->dev;
u32 status, operand;
int rc;
lockdep_assert_held(&idxd->dev_lock);
dev_dbg(dev, "Disabling WQ %d\n", wq->id);
if (wq->state != IDXD_WQ_ENABLED) {
dev_dbg(dev, "WQ %d in wrong state: %d\n", wq->id, wq->state);
return 0;
}
operand = BIT(wq->id % 16) | ((wq->id / 16) << 16);
rc = idxd_cmd_send(idxd, IDXD_CMD_DISABLE_WQ, operand);
if (rc < 0)
return rc;
rc = idxd_cmd_wait(idxd, &status, IDXD_REG_TIMEOUT);
if (rc < 0)
return rc;
if (status != IDXD_CMDSTS_SUCCESS) {
dev_dbg(dev, "WQ disable failed: %#x\n", status);
return -ENXIO;
}
wq->state = IDXD_WQ_DISABLED;
dev_dbg(dev, "WQ %d disabled\n", wq->id);
return 0;
}
int idxd_wq_map_portal(struct idxd_wq *wq)
{
struct idxd_device *idxd = wq->idxd;
struct pci_dev *pdev = idxd->pdev;
struct device *dev = &pdev->dev;
resource_size_t start;
start = pci_resource_start(pdev, IDXD_WQ_BAR);
start = start + wq->id * IDXD_PORTAL_SIZE;
wq->dportal = devm_ioremap(dev, start, IDXD_PORTAL_SIZE);
if (!wq->dportal)
return -ENOMEM;
dev_dbg(dev, "wq %d portal mapped at %p\n", wq->id, wq->dportal);
return 0;
}
void idxd_wq_unmap_portal(struct idxd_wq *wq)
{
struct device *dev = &wq->idxd->pdev->dev;
devm_iounmap(dev, wq->dportal);
}
/* Device control bits */
static inline bool idxd_is_enabled(struct idxd_device *idxd)
{
union gensts_reg gensts;
gensts.bits = ioread32(idxd->reg_base + IDXD_GENSTATS_OFFSET);
if (gensts.state == IDXD_DEVICE_STATE_ENABLED)
return true;
return false;
}
static int idxd_cmd_wait(struct idxd_device *idxd, u32 *status, int timeout)
{
u32 sts, to = timeout;
lockdep_assert_held(&idxd->dev_lock);
sts = ioread32(idxd->reg_base + IDXD_CMDSTS_OFFSET);
while (sts & IDXD_CMDSTS_ACTIVE && --to) {
cpu_relax();
sts = ioread32(idxd->reg_base + IDXD_CMDSTS_OFFSET);
}
if (to == 0 && sts & IDXD_CMDSTS_ACTIVE) {
dev_warn(&idxd->pdev->dev, "%s timed out!\n", __func__);
*status = 0;
return -EBUSY;
}
*status = sts;
return 0;
}
static int idxd_cmd_send(struct idxd_device *idxd, int cmd_code, u32 operand)
{
union idxd_command_reg cmd;
int rc;
u32 status;
lockdep_assert_held(&idxd->dev_lock);
rc = idxd_cmd_wait(idxd, &status, IDXD_REG_TIMEOUT);
if (rc < 0)
return rc;
memset(&cmd, 0, sizeof(cmd));
cmd.cmd = cmd_code;
cmd.operand = operand;
dev_dbg(&idxd->pdev->dev, "%s: sending cmd: %#x op: %#x\n",
__func__, cmd_code, operand);
iowrite32(cmd.bits, idxd->reg_base + IDXD_CMD_OFFSET);
return 0;
}
int idxd_device_enable(struct idxd_device *idxd)
{
struct device *dev = &idxd->pdev->dev;
int rc;
u32 status;
lockdep_assert_held(&idxd->dev_lock);
if (idxd_is_enabled(idxd)) {
dev_dbg(dev, "Device already enabled\n");
return -ENXIO;
}
rc = idxd_cmd_send(idxd, IDXD_CMD_ENABLE_DEVICE, 0);
if (rc < 0)
return rc;
rc = idxd_cmd_wait(idxd, &status, IDXD_REG_TIMEOUT);
if (rc < 0)
return rc;
/* If the command is successful or if the device was enabled */
if (status != IDXD_CMDSTS_SUCCESS &&
status != IDXD_CMDSTS_ERR_DEV_ENABLED) {
dev_dbg(dev, "%s: err_code: %#x\n", __func__, status);
return -ENXIO;
}
idxd->state = IDXD_DEV_ENABLED;
return 0;
}
int idxd_device_disable(struct idxd_device *idxd)
{
struct device *dev = &idxd->pdev->dev;
int rc;
u32 status;
lockdep_assert_held(&idxd->dev_lock);
if (!idxd_is_enabled(idxd)) {
dev_dbg(dev, "Device is not enabled\n");
return 0;
}
rc = idxd_cmd_send(idxd, IDXD_CMD_DISABLE_DEVICE, 0);
if (rc < 0)
return rc;
rc = idxd_cmd_wait(idxd, &status, IDXD_REG_TIMEOUT);
if (rc < 0)
return rc;
/* If the command is successful or if the device was disabled */
if (status != IDXD_CMDSTS_SUCCESS &&
!(status & IDXD_CMDSTS_ERR_DIS_DEV_EN)) {
dev_dbg(dev, "%s: err_code: %#x\n", __func__, status);
rc = -ENXIO;
return rc;
}
idxd->state = IDXD_DEV_CONF_READY;
return 0;
}
int __idxd_device_reset(struct idxd_device *idxd)
{
u32 status;
int rc;
rc = idxd_cmd_send(idxd, IDXD_CMD_RESET_DEVICE, 0);
if (rc < 0)
return rc;
rc = idxd_cmd_wait(idxd, &status, IDXD_REG_TIMEOUT);
if (rc < 0)
return rc;
return 0;
}
int idxd_device_reset(struct idxd_device *idxd)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&idxd->dev_lock, flags);
rc = __idxd_device_reset(idxd);
spin_unlock_irqrestore(&idxd->dev_lock, flags);
return rc;
}
/* Device configuration bits */
static void idxd_group_config_write(struct idxd_group *group)
{
struct idxd_device *idxd = group->idxd;
struct device *dev = &idxd->pdev->dev;
int i;
u32 grpcfg_offset;
dev_dbg(dev, "Writing group %d cfg registers\n", group->id);
/* setup GRPWQCFG */
for (i = 0; i < 4; i++) {
grpcfg_offset = idxd->grpcfg_offset +
group->id * 64 + i * sizeof(u64);
iowrite64(group->grpcfg.wqs[i],
idxd->reg_base + grpcfg_offset);
dev_dbg(dev, "GRPCFG wq[%d:%d: %#x]: %#llx\n",
group->id, i, grpcfg_offset,
ioread64(idxd->reg_base + grpcfg_offset));
}
/* setup GRPENGCFG */
grpcfg_offset = idxd->grpcfg_offset + group->id * 64 + 32;
iowrite64(group->grpcfg.engines, idxd->reg_base + grpcfg_offset);
dev_dbg(dev, "GRPCFG engs[%d: %#x]: %#llx\n", group->id,
grpcfg_offset, ioread64(idxd->reg_base + grpcfg_offset));
/* setup GRPFLAGS */
grpcfg_offset = idxd->grpcfg_offset + group->id * 64 + 40;
iowrite32(group->grpcfg.flags.bits, idxd->reg_base + grpcfg_offset);
dev_dbg(dev, "GRPFLAGS flags[%d: %#x]: %#x\n",
group->id, grpcfg_offset,
ioread32(idxd->reg_base + grpcfg_offset));
}
static int idxd_groups_config_write(struct idxd_device *idxd)
{
union gencfg_reg reg;
int i;
struct device *dev = &idxd->pdev->dev;
/* Setup bandwidth token limit */
if (idxd->token_limit) {
reg.bits = ioread32(idxd->reg_base + IDXD_GENCFG_OFFSET);
reg.token_limit = idxd->token_limit;
iowrite32(reg.bits, idxd->reg_base + IDXD_GENCFG_OFFSET);
}
dev_dbg(dev, "GENCFG(%#x): %#x\n", IDXD_GENCFG_OFFSET,
ioread32(idxd->reg_base + IDXD_GENCFG_OFFSET));
for (i = 0; i < idxd->max_groups; i++) {
struct idxd_group *group = &idxd->groups[i];
idxd_group_config_write(group);
}
return 0;
}
static int idxd_wq_config_write(struct idxd_wq *wq)
{
struct idxd_device *idxd = wq->idxd;
struct device *dev = &idxd->pdev->dev;
u32 wq_offset;
int i;
if (!wq->group)
return 0;
memset(&wq->wqcfg, 0, sizeof(union wqcfg));
/* byte 0-3 */
wq->wqcfg.wq_size = wq->size;
if (wq->size == 0) {
dev_warn(dev, "Incorrect work queue size: 0\n");
return -EINVAL;
}
/* bytes 4-7 */
wq->wqcfg.wq_thresh = wq->threshold;
/* byte 8-11 */
wq->wqcfg.priv = !!(wq->type == IDXD_WQT_KERNEL);
wq->wqcfg.mode = 1;
wq->wqcfg.priority = wq->priority;
/* bytes 12-15 */
wq->wqcfg.max_xfer_shift = idxd->hw.gen_cap.max_xfer_shift;
wq->wqcfg.max_batch_shift = idxd->hw.gen_cap.max_batch_shift;
dev_dbg(dev, "WQ %d CFGs\n", wq->id);
for (i = 0; i < 8; i++) {
wq_offset = idxd->wqcfg_offset + wq->id * 32 + i * sizeof(u32);
iowrite32(wq->wqcfg.bits[i], idxd->reg_base + wq_offset);
dev_dbg(dev, "WQ[%d][%d][%#x]: %#x\n",
wq->id, i, wq_offset,
ioread32(idxd->reg_base + wq_offset));
}
return 0;
}
static int idxd_wqs_config_write(struct idxd_device *idxd)
{
int i, rc;
for (i = 0; i < idxd->max_wqs; i++) {
struct idxd_wq *wq = &idxd->wqs[i];
rc = idxd_wq_config_write(wq);
if (rc < 0)
return rc;
}
return 0;
}
static void idxd_group_flags_setup(struct idxd_device *idxd)
{
int i;
/* TC-A 0 and TC-B 1 should be defaults */
for (i = 0; i < idxd->max_groups; i++) {
struct idxd_group *group = &idxd->groups[i];
if (group->tc_a == -1)
group->tc_a = group->grpcfg.flags.tc_a = 0;
else
group->grpcfg.flags.tc_a = group->tc_a;
if (group->tc_b == -1)
group->tc_b = group->grpcfg.flags.tc_b = 1;
else
group->grpcfg.flags.tc_b = group->tc_b;
group->grpcfg.flags.use_token_limit = group->use_token_limit;
group->grpcfg.flags.tokens_reserved = group->tokens_reserved;
if (group->tokens_allowed)
group->grpcfg.flags.tokens_allowed =
group->tokens_allowed;
else
group->grpcfg.flags.tokens_allowed = idxd->max_tokens;
}
}
static int idxd_engines_setup(struct idxd_device *idxd)
{
int i, engines = 0;
struct idxd_engine *eng;
struct idxd_group *group;
for (i = 0; i < idxd->max_groups; i++) {
group = &idxd->groups[i];
group->grpcfg.engines = 0;
}
for (i = 0; i < idxd->max_engines; i++) {
eng = &idxd->engines[i];
group = eng->group;
if (!group)
continue;
group->grpcfg.engines |= BIT(eng->id);
engines++;
}
if (!engines)
return -EINVAL;
return 0;
}
static int idxd_wqs_setup(struct idxd_device *idxd)
{
struct idxd_wq *wq;
struct idxd_group *group;
int i, j, configured = 0;
struct device *dev = &idxd->pdev->dev;
for (i = 0; i < idxd->max_groups; i++) {
group = &idxd->groups[i];
for (j = 0; j < 4; j++)
group->grpcfg.wqs[j] = 0;
}
for (i = 0; i < idxd->max_wqs; i++) {
wq = &idxd->wqs[i];
group = wq->group;
if (!wq->group)
continue;
if (!wq->size)
continue;
if (!wq_dedicated(wq)) {
dev_warn(dev, "No shared workqueue support.\n");
return -EINVAL;
}
group->grpcfg.wqs[wq->id / 64] |= BIT(wq->id % 64);
configured++;
}
if (configured == 0)
return -EINVAL;
return 0;
}
int idxd_device_config(struct idxd_device *idxd)
{
int rc;
lockdep_assert_held(&idxd->dev_lock);
rc = idxd_wqs_setup(idxd);
if (rc < 0)
return rc;
rc = idxd_engines_setup(idxd);
if (rc < 0)
return rc;
idxd_group_flags_setup(idxd);
rc = idxd_wqs_config_write(idxd);
if (rc < 0)
return rc;
rc = idxd_groups_config_write(idxd);
if (rc < 0)
return rc;
return 0;
}