2023-08-30 17:31:07 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* Intel On Demand (Software Defined Silicon) driver
|
|
|
|
*
|
|
|
|
* Copyright (c) 2022, Intel Corporation.
|
|
|
|
* All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Author: "David E. Box" <david.e.box@linux.intel.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/auxiliary_bus.h>
|
|
|
|
#include <linux/bits.h>
|
|
|
|
#include <linux/bitfield.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/iopoll.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/sysfs.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
|
|
|
|
#include "vsec.h"
|
|
|
|
|
|
|
|
#define ACCESS_TYPE_BARID 2
|
|
|
|
#define ACCESS_TYPE_LOCAL 3
|
|
|
|
|
|
|
|
#define SDSI_MIN_SIZE_DWORDS 276
|
|
|
|
#define SDSI_SIZE_MAILBOX 1024
|
|
|
|
#define SDSI_SIZE_REGS 80
|
|
|
|
#define SDSI_SIZE_CMD sizeof(u64)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write messages are currently up to the size of the mailbox
|
|
|
|
* while read messages are up to 4 times the size of the
|
|
|
|
* mailbox, sent in packets
|
|
|
|
*/
|
|
|
|
#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX
|
|
|
|
#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4)
|
|
|
|
|
|
|
|
#define SDSI_ENABLED_FEATURES_OFFSET 16
|
|
|
|
#define SDSI_FEATURE_SDSI BIT(3)
|
|
|
|
#define SDSI_FEATURE_METERING BIT(26)
|
|
|
|
|
|
|
|
#define SDSI_SOCKET_ID_OFFSET 64
|
|
|
|
#define SDSI_SOCKET_ID GENMASK(3, 0)
|
|
|
|
|
|
|
|
#define SDSI_MBOX_CMD_SUCCESS 0x40
|
|
|
|
#define SDSI_MBOX_CMD_TIMEOUT 0x80
|
|
|
|
|
2023-10-24 12:59:35 +02:00
|
|
|
#define MBOX_TIMEOUT_US 500000
|
2023-08-30 17:31:07 +02:00
|
|
|
#define MBOX_TIMEOUT_ACQUIRE_US 1000
|
|
|
|
#define MBOX_POLLING_PERIOD_US 100
|
|
|
|
#define MBOX_ACQUIRE_NUM_RETRIES 5
|
|
|
|
#define MBOX_ACQUIRE_RETRY_DELAY_MS 500
|
|
|
|
#define MBOX_MAX_PACKETS 4
|
|
|
|
|
|
|
|
#define MBOX_OWNER_NONE 0x00
|
|
|
|
#define MBOX_OWNER_INBAND 0x01
|
|
|
|
|
|
|
|
#define CTRL_RUN_BUSY BIT(0)
|
|
|
|
#define CTRL_READ_WRITE BIT(1)
|
|
|
|
#define CTRL_SOM BIT(2)
|
|
|
|
#define CTRL_EOM BIT(3)
|
|
|
|
#define CTRL_OWNER GENMASK(5, 4)
|
|
|
|
#define CTRL_COMPLETE BIT(6)
|
|
|
|
#define CTRL_READY BIT(7)
|
|
|
|
#define CTRL_STATUS GENMASK(15, 8)
|
|
|
|
#define CTRL_PACKET_SIZE GENMASK(31, 16)
|
|
|
|
#define CTRL_MSG_SIZE GENMASK(63, 48)
|
|
|
|
|
|
|
|
#define DISC_TABLE_SIZE 12
|
|
|
|
#define DT_ACCESS_TYPE GENMASK(3, 0)
|
|
|
|
#define DT_SIZE GENMASK(27, 12)
|
|
|
|
#define DT_TBIR GENMASK(2, 0)
|
|
|
|
#define DT_OFFSET(v) ((v) & GENMASK(31, 3))
|
|
|
|
|
|
|
|
#define SDSI_GUID_V1 0x006DD191
|
|
|
|
#define GUID_V1_CNTRL_SIZE 8
|
|
|
|
#define GUID_V1_REGS_SIZE 72
|
|
|
|
#define SDSI_GUID_V2 0xF210D9EF
|
|
|
|
#define GUID_V2_CNTRL_SIZE 16
|
|
|
|
#define GUID_V2_REGS_SIZE 80
|
|
|
|
|
|
|
|
enum sdsi_command {
|
|
|
|
SDSI_CMD_PROVISION_AKC = 0x0004,
|
|
|
|
SDSI_CMD_PROVISION_CAP = 0x0008,
|
|
|
|
SDSI_CMD_READ_STATE = 0x0010,
|
|
|
|
SDSI_CMD_READ_METER = 0x0014,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct sdsi_mbox_info {
|
|
|
|
u64 *payload;
|
|
|
|
void *buffer;
|
|
|
|
int size;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct disc_table {
|
|
|
|
u32 access_info;
|
|
|
|
u32 guid;
|
|
|
|
u32 offset;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct sdsi_priv {
|
|
|
|
struct mutex mb_lock; /* Mailbox access lock */
|
|
|
|
struct device *dev;
|
|
|
|
void __iomem *control_addr;
|
|
|
|
void __iomem *mbox_addr;
|
|
|
|
void __iomem *regs_addr;
|
|
|
|
int control_size;
|
|
|
|
int maibox_size;
|
|
|
|
int registers_size;
|
|
|
|
u32 guid;
|
|
|
|
u32 features;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* SDSi mailbox operations must be performed using 64bit mov instructions */
|
|
|
|
static __always_inline void
|
|
|
|
sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes)
|
|
|
|
{
|
|
|
|
size_t count = count_bytes / sizeof(*to);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
writeq(from[i], &to[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __always_inline void
|
|
|
|
sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes)
|
|
|
|
{
|
|
|
|
size_t count = count_bytes / sizeof(*to);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
to[i] = readq(&from[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void sdsi_complete_transaction(struct sdsi_priv *priv)
|
|
|
|
{
|
|
|
|
u64 control = FIELD_PREP(CTRL_COMPLETE, 1);
|
|
|
|
|
|
|
|
lockdep_assert_held(&priv->mb_lock);
|
|
|
|
writeq(control, priv->control_addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_status_to_errno(u32 status)
|
|
|
|
{
|
|
|
|
switch (status) {
|
|
|
|
case SDSI_MBOX_CMD_SUCCESS:
|
|
|
|
return 0;
|
|
|
|
case SDSI_MBOX_CMD_TIMEOUT:
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
default:
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
|
|
|
|
size_t *data_size)
|
|
|
|
{
|
|
|
|
struct device *dev = priv->dev;
|
|
|
|
u32 total, loop, eom, status, message_size;
|
|
|
|
u64 control;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
lockdep_assert_held(&priv->mb_lock);
|
|
|
|
|
|
|
|
/* Format and send the read command */
|
|
|
|
control = FIELD_PREP(CTRL_EOM, 1) |
|
|
|
|
FIELD_PREP(CTRL_SOM, 1) |
|
|
|
|
FIELD_PREP(CTRL_RUN_BUSY, 1) |
|
|
|
|
FIELD_PREP(CTRL_PACKET_SIZE, info->size);
|
|
|
|
writeq(control, priv->control_addr);
|
|
|
|
|
|
|
|
/* For reads, data sizes that are larger than the mailbox size are read in packets. */
|
|
|
|
total = 0;
|
|
|
|
loop = 0;
|
|
|
|
do {
|
|
|
|
void *buf = info->buffer + (SDSI_SIZE_MAILBOX * loop);
|
|
|
|
u32 packet_size;
|
|
|
|
|
|
|
|
/* Poll on ready bit */
|
|
|
|
ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY,
|
|
|
|
MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
|
|
|
|
eom = FIELD_GET(CTRL_EOM, control);
|
|
|
|
status = FIELD_GET(CTRL_STATUS, control);
|
|
|
|
packet_size = FIELD_GET(CTRL_PACKET_SIZE, control);
|
|
|
|
message_size = FIELD_GET(CTRL_MSG_SIZE, control);
|
|
|
|
|
|
|
|
ret = sdsi_status_to_errno(status);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Only the last packet can be less than the mailbox size. */
|
|
|
|
if (!eom && packet_size != SDSI_SIZE_MAILBOX) {
|
|
|
|
dev_err(dev, "Invalid packet size\n");
|
|
|
|
ret = -EPROTO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packet_size > SDSI_SIZE_MAILBOX) {
|
|
|
|
dev_err(dev, "Packet size too large\n");
|
|
|
|
ret = -EPROTO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
sdsi_memcpy64_fromio(buf, priv->mbox_addr, round_up(packet_size, SDSI_SIZE_CMD));
|
|
|
|
|
|
|
|
total += packet_size;
|
|
|
|
|
|
|
|
sdsi_complete_transaction(priv);
|
|
|
|
} while (!eom && ++loop < MBOX_MAX_PACKETS);
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
sdsi_complete_transaction(priv);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!eom) {
|
|
|
|
dev_err(dev, "Exceeded read attempts\n");
|
|
|
|
return -EPROTO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Message size check is only valid for multi-packet transfers */
|
|
|
|
if (loop && total != message_size)
|
|
|
|
dev_warn(dev, "Read count %u differs from expected count %u\n",
|
|
|
|
total, message_size);
|
|
|
|
|
|
|
|
*data_size = total;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
|
|
|
|
{
|
|
|
|
u64 control;
|
|
|
|
u32 status;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
lockdep_assert_held(&priv->mb_lock);
|
|
|
|
|
|
|
|
/* Write rest of the payload */
|
|
|
|
sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1,
|
|
|
|
info->size - SDSI_SIZE_CMD);
|
|
|
|
|
|
|
|
/* Format and send the write command */
|
|
|
|
control = FIELD_PREP(CTRL_EOM, 1) |
|
|
|
|
FIELD_PREP(CTRL_SOM, 1) |
|
|
|
|
FIELD_PREP(CTRL_RUN_BUSY, 1) |
|
|
|
|
FIELD_PREP(CTRL_READ_WRITE, 1) |
|
|
|
|
FIELD_PREP(CTRL_PACKET_SIZE, info->size);
|
|
|
|
writeq(control, priv->control_addr);
|
|
|
|
|
|
|
|
/* Poll on ready bit */
|
|
|
|
ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY,
|
|
|
|
MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
goto release_mbox;
|
|
|
|
|
|
|
|
status = FIELD_GET(CTRL_STATUS, control);
|
|
|
|
ret = sdsi_status_to_errno(status);
|
|
|
|
|
|
|
|
release_mbox:
|
|
|
|
sdsi_complete_transaction(priv);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
|
|
|
|
{
|
|
|
|
u64 control;
|
|
|
|
u32 owner;
|
|
|
|
int ret, retries = 0;
|
|
|
|
|
|
|
|
lockdep_assert_held(&priv->mb_lock);
|
|
|
|
|
|
|
|
/* Check mailbox is available */
|
|
|
|
control = readq(priv->control_addr);
|
|
|
|
owner = FIELD_GET(CTRL_OWNER, control);
|
|
|
|
if (owner != MBOX_OWNER_NONE)
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there has been no recent transaction and no one owns the mailbox,
|
|
|
|
* we should acquire it in under 1ms. However, if we've accessed it
|
|
|
|
* recently it may take up to 2.1 seconds to acquire it again.
|
|
|
|
*/
|
|
|
|
do {
|
|
|
|
/* Write first qword of payload */
|
|
|
|
writeq(info->payload[0], priv->mbox_addr);
|
|
|
|
|
|
|
|
/* Check for ownership */
|
|
|
|
ret = readq_poll_timeout(priv->control_addr, control,
|
|
|
|
FIELD_GET(CTRL_OWNER, control) == MBOX_OWNER_INBAND,
|
|
|
|
MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US);
|
|
|
|
|
|
|
|
if (FIELD_GET(CTRL_OWNER, control) == MBOX_OWNER_NONE &&
|
|
|
|
retries++ < MBOX_ACQUIRE_NUM_RETRIES) {
|
|
|
|
msleep(MBOX_ACQUIRE_RETRY_DELAY_MS);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Either we got it or someone else did. */
|
|
|
|
break;
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
lockdep_assert_held(&priv->mb_lock);
|
|
|
|
|
|
|
|
ret = sdsi_mbox_acquire(priv, info);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return sdsi_mbox_cmd_write(priv, info);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
lockdep_assert_held(&priv->mb_lock);
|
|
|
|
|
|
|
|
ret = sdsi_mbox_acquire(priv, info);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return sdsi_mbox_cmd_read(priv, info, data_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count,
|
|
|
|
enum sdsi_command command)
|
|
|
|
{
|
|
|
|
struct sdsi_mbox_info info;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD))
|
|
|
|
return -EOVERFLOW;
|
|
|
|
|
|
|
|
/* Qword aligned message + command qword */
|
|
|
|
info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD;
|
|
|
|
|
|
|
|
info.payload = kzalloc(info.size, GFP_KERNEL);
|
|
|
|
if (!info.payload)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* Copy message to payload buffer */
|
|
|
|
memcpy(info.payload, buf, count);
|
|
|
|
|
|
|
|
/* Command is last qword of payload buffer */
|
|
|
|
info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command;
|
|
|
|
|
|
|
|
ret = mutex_lock_interruptible(&priv->mb_lock);
|
|
|
|
if (ret)
|
|
|
|
goto free_payload;
|
|
|
|
ret = sdsi_mbox_write(priv, &info);
|
|
|
|
mutex_unlock(&priv->mb_lock);
|
|
|
|
|
|
|
|
free_payload:
|
|
|
|
kfree(info.payload);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (off)
|
|
|
|
return -ESPIPE;
|
|
|
|
|
|
|
|
return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC);
|
|
|
|
}
|
|
|
|
static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG);
|
|
|
|
|
|
|
|
static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (off)
|
|
|
|
return -ESPIPE;
|
|
|
|
|
|
|
|
return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP);
|
|
|
|
}
|
|
|
|
static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG);
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
certificate_read(u64 command, struct sdsi_priv *priv, char *buf, loff_t off,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct sdsi_mbox_info info;
|
|
|
|
size_t size;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (off)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Buffer for return data */
|
|
|
|
info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL);
|
|
|
|
if (!info.buffer)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
info.payload = &command;
|
|
|
|
info.size = sizeof(command);
|
|
|
|
|
|
|
|
ret = mutex_lock_interruptible(&priv->mb_lock);
|
|
|
|
if (ret)
|
|
|
|
goto free_buffer;
|
|
|
|
ret = sdsi_mbox_read(priv, &info, &size);
|
|
|
|
mutex_unlock(&priv->mb_lock);
|
|
|
|
if (ret < 0)
|
|
|
|
goto free_buffer;
|
|
|
|
|
|
|
|
if (size > count)
|
|
|
|
size = count;
|
|
|
|
|
|
|
|
memcpy(buf, info.buffer, size);
|
|
|
|
|
|
|
|
free_buffer:
|
|
|
|
kfree(info.buffer);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
state_certificate_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return certificate_read(SDSI_CMD_READ_STATE, priv, buf, off, count);
|
|
|
|
}
|
|
|
|
static BIN_ATTR_ADMIN_RO(state_certificate, SDSI_SIZE_READ_MSG);
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
meter_certificate_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return certificate_read(SDSI_CMD_READ_METER, priv, buf, off, count);
|
|
|
|
}
|
|
|
|
static BIN_ATTR_ADMIN_RO(meter_certificate, SDSI_SIZE_READ_MSG);
|
|
|
|
|
|
|
|
static ssize_t registers_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
void __iomem *addr = priv->regs_addr;
|
|
|
|
int size = priv->registers_size;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The check below is performed by the sysfs caller based on the static
|
|
|
|
* file size. But this may be greater than the actual size which is based
|
|
|
|
* on the GUID. So check here again based on actual size before reading.
|
|
|
|
*/
|
|
|
|
if (off >= size)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (off + count > size)
|
|
|
|
count = size - off;
|
|
|
|
|
|
|
|
memcpy_fromio(buf, addr + off, count);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
static BIN_ATTR_ADMIN_RO(registers, SDSI_SIZE_REGS);
|
|
|
|
|
|
|
|
static struct bin_attribute *sdsi_bin_attrs[] = {
|
|
|
|
&bin_attr_registers,
|
|
|
|
&bin_attr_state_certificate,
|
|
|
|
&bin_attr_meter_certificate,
|
|
|
|
&bin_attr_provision_akc,
|
|
|
|
&bin_attr_provision_cap,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static umode_t
|
|
|
|
sdsi_battr_is_visible(struct kobject *kobj, struct bin_attribute *attr, int n)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
/* Registers file is always readable if the device is present */
|
|
|
|
if (attr == &bin_attr_registers)
|
|
|
|
return attr->attr.mode;
|
|
|
|
|
|
|
|
/* All other attributes not visible if BIOS has not enabled On Demand */
|
|
|
|
if (!(priv->features & SDSI_FEATURE_SDSI))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (attr == &bin_attr_meter_certificate)
|
|
|
|
return (priv->features & SDSI_FEATURE_METERING) ?
|
|
|
|
attr->attr.mode : 0;
|
|
|
|
|
|
|
|
return attr->attr.mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return sysfs_emit(buf, "0x%x\n", priv->guid);
|
|
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(guid);
|
|
|
|
|
|
|
|
static struct attribute *sdsi_attrs[] = {
|
|
|
|
&dev_attr_guid.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group sdsi_group = {
|
|
|
|
.attrs = sdsi_attrs,
|
|
|
|
.bin_attrs = sdsi_bin_attrs,
|
|
|
|
.is_bin_visible = sdsi_battr_is_visible,
|
|
|
|
};
|
|
|
|
__ATTRIBUTE_GROUPS(sdsi);
|
|
|
|
|
|
|
|
static int sdsi_get_layout(struct sdsi_priv *priv, struct disc_table *table)
|
|
|
|
{
|
|
|
|
switch (table->guid) {
|
|
|
|
case SDSI_GUID_V1:
|
|
|
|
priv->control_size = GUID_V1_CNTRL_SIZE;
|
|
|
|
priv->registers_size = GUID_V1_REGS_SIZE;
|
|
|
|
break;
|
|
|
|
case SDSI_GUID_V2:
|
|
|
|
priv->control_size = GUID_V2_CNTRL_SIZE;
|
|
|
|
priv->registers_size = GUID_V2_REGS_SIZE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(priv->dev, "Unrecognized GUID 0x%x\n", table->guid);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent,
|
|
|
|
struct disc_table *disc_table, struct resource *disc_res)
|
|
|
|
{
|
|
|
|
u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info);
|
|
|
|
u32 size = FIELD_GET(DT_SIZE, disc_table->access_info);
|
|
|
|
u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset);
|
|
|
|
u32 offset = DT_OFFSET(disc_table->offset);
|
|
|
|
struct resource res = {};
|
|
|
|
|
|
|
|
/* Starting location of SDSi MMIO region based on access type */
|
|
|
|
switch (access_type) {
|
|
|
|
case ACCESS_TYPE_LOCAL:
|
|
|
|
if (tbir) {
|
|
|
|
dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n",
|
|
|
|
tbir, access_type);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For access_type LOCAL, the base address is as follows:
|
|
|
|
* base address = end of discovery region + base offset + 1
|
|
|
|
*/
|
|
|
|
res.start = disc_res->end + offset + 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ACCESS_TYPE_BARID:
|
|
|
|
res.start = pci_resource_start(parent, tbir) + offset;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
dev_err(priv->dev, "Unrecognized access_type %u\n", access_type);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
res.end = res.start + size * sizeof(u32) - 1;
|
|
|
|
res.flags = IORESOURCE_MEM;
|
|
|
|
|
|
|
|
priv->control_addr = devm_ioremap_resource(priv->dev, &res);
|
|
|
|
if (IS_ERR(priv->control_addr))
|
|
|
|
return PTR_ERR(priv->control_addr);
|
|
|
|
|
|
|
|
priv->mbox_addr = priv->control_addr + priv->control_size;
|
|
|
|
priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX;
|
|
|
|
|
|
|
|
priv->features = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
|
|
|
|
{
|
|
|
|
struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev);
|
|
|
|
struct disc_table disc_table;
|
|
|
|
struct resource *disc_res;
|
|
|
|
void __iomem *disc_addr;
|
|
|
|
struct sdsi_priv *priv;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
|
|
if (!priv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
priv->dev = &auxdev->dev;
|
|
|
|
mutex_init(&priv->mb_lock);
|
|
|
|
auxiliary_set_drvdata(auxdev, priv);
|
|
|
|
|
|
|
|
/* Get the SDSi discovery table */
|
|
|
|
disc_res = &intel_cap_dev->resource[0];
|
|
|
|
disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res);
|
|
|
|
if (IS_ERR(disc_addr))
|
|
|
|
return PTR_ERR(disc_addr);
|
|
|
|
|
|
|
|
memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE);
|
|
|
|
|
|
|
|
priv->guid = disc_table.guid;
|
|
|
|
|
|
|
|
/* Get guid based layout info */
|
|
|
|
ret = sdsi_get_layout(priv, &disc_table);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* Map the SDSi mailbox registers */
|
|
|
|
ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct auxiliary_device_id sdsi_aux_id_table[] = {
|
|
|
|
{ .name = "intel_vsec.sdsi" },
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table);
|
|
|
|
|
|
|
|
static struct auxiliary_driver sdsi_aux_driver = {
|
|
|
|
.driver = {
|
|
|
|
.dev_groups = sdsi_groups,
|
|
|
|
},
|
|
|
|
.id_table = sdsi_aux_id_table,
|
|
|
|
.probe = sdsi_probe,
|
|
|
|
/* No remove. All resources are handled under devm */
|
|
|
|
};
|
|
|
|
module_auxiliary_driver(sdsi_aux_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
|
|
|
|
MODULE_DESCRIPTION("Intel On Demand (SDSi) driver");
|
|
|
|
MODULE_LICENSE("GPL");
|