blob: f6ba540863504103b1aa1c4c9d9762dff4d64516 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Platform device driver for the Google GSA core.
*
* Copyright (C) 2020 Google LLC
*/
#include <linux/dma-mapping.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gsa/gsa_aoc.h>
#include <linux/gsa/gsa_dsp.h>
#include <linux/gsa/gsa_kdn.h>
#include <linux/gsa/gsa_sjtag.h>
#include <linux/gsa/gsa_tpu.h>
#include "gsa_log.h"
#include "gsa_mbox.h"
#include "gsa_priv.h"
#include "gsa_tz.h"
#include "hwmgr-ipc.h"
struct gsa_dev_state {
struct device *dev;
struct gsa_mbox *mb;
dma_addr_t bb_da;
void *bb_va;
size_t bb_sz;
struct mutex bb_lock; /* protects access to bounce buffer */
struct gsa_tz_chan_ctx aoc_srv;
struct gsa_tz_chan_ctx tpu_srv;
struct gsa_tz_chan_ctx dsp_srv;
struct gsa_log *log;
};
/*
* Internal command interface
*/
int gsa_send_cmd(struct device *dev, u32 cmd, u32 *req, u32 req_argc,
u32 *rsp, u32 rsp_argc)
{
struct platform_device *pdev = to_platform_device(dev);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_send_mbox_cmd(s->mb, cmd, req, req_argc, rsp, rsp_argc);
};
EXPORT_SYMBOL_GPL(gsa_send_cmd);
int gsa_send_simple_cmd(struct device *dev, u32 cmd)
{
return gsa_send_cmd(dev, cmd, NULL, 0, NULL, 0);
}
EXPORT_SYMBOL_GPL(gsa_send_simple_cmd);
int gsa_send_one_arg_cmd(struct device *dev, u32 cmd, u32 arg)
{
return gsa_send_cmd(dev, cmd, &arg, 1, NULL, 0);
}
EXPORT_SYMBOL_GPL(gsa_send_one_arg_cmd);
static int gsa_send_load_img_cmd(struct device *dev, uint32_t cmd,
dma_addr_t hdr_da, phys_addr_t body_pa)
{
u32 req[4];
struct platform_device *pdev = to_platform_device(dev);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
req[IMG_LOADER_HEADER_ADDR_LO_IDX] = (u32)hdr_da;
req[IMG_LOADER_HEADER_ADDR_HI_IDX] = (u32)(hdr_da >> 32);
req[IMG_LOADER_BODY_ADDR_LO_IDX] = (u32)body_pa;
req[IMG_LOADER_BODY_ADDR_HI_IDX] = (u32)(body_pa >> 32);
return gsa_send_mbox_cmd(s->mb, cmd, req, 4, NULL, 0);
}
static int gsa_tz_send_hwmgr_state_cmd(struct gsa_tz_chan_ctx *ctx, u32 cmd)
{
int rc;
struct {
struct hwmgr_rsp_hdr hdr;
struct hwmgr_state_cmd_rsp rsp;
} rsp_msg;
struct {
struct hwmgr_req_hdr hdr;
struct hwmgr_state_cmd_req req;
} req_msg;
req_msg.hdr.cmd = HWMGR_CMD_STATE_CMD;
req_msg.req.cmd = cmd;
rc = gsa_tz_chan_msg_xchg(ctx,
&req_msg, sizeof(req_msg),
&rsp_msg, sizeof(rsp_msg));
if (rc != sizeof(rsp_msg)) {
return -EIO;
}
if (rsp_msg.hdr.cmd != (req_msg.hdr.cmd | HWMGR_CMD_RESP)) {
return -EIO;
}
if (rsp_msg.hdr.err) {
return -EIO;
}
return rsp_msg.rsp.state;
}
static int gsa_tz_send_hwmgr_unload_fw_image_cmd(struct gsa_tz_chan_ctx *ctx)
{
int rc;
struct {
struct hwmgr_rsp_hdr hdr;
} rsp_msg;
struct {
struct hwmgr_req_hdr hdr;
} req_msg;
req_msg.hdr.cmd = HWMGR_CMD_UNLOAD_IMG;
rc = gsa_tz_chan_msg_xchg(ctx,
&req_msg, sizeof(req_msg),
&rsp_msg, sizeof(rsp_msg));
if (rc != sizeof(rsp_msg)) {
return -EIO;
}
if (rsp_msg.hdr.cmd != (req_msg.hdr.cmd | HWMGR_CMD_RESP)) {
return -EIO;
}
if (rsp_msg.hdr.err) {
return -EIO;
}
return 0;
}
/*
* External AOC interface
*/
int gsa_load_aoc_fw_image(struct device *gsa,
dma_addr_t img_meta,
phys_addr_t img_body)
{
return gsa_send_load_img_cmd(gsa, GSA_MB_CMD_LOAD_AOC_FW_IMG,
img_meta, img_body);
}
EXPORT_SYMBOL_GPL(gsa_load_aoc_fw_image);
int gsa_unload_aoc_fw_image(struct device *gsa)
{
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_tz_send_hwmgr_unload_fw_image_cmd(&s->aoc_srv);
}
EXPORT_SYMBOL_GPL(gsa_unload_aoc_fw_image);
int gsa_send_aoc_cmd(struct device *gsa, enum gsa_aoc_cmd arg)
{
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_tz_send_hwmgr_state_cmd(&s->aoc_srv, arg);
}
EXPORT_SYMBOL_GPL(gsa_send_aoc_cmd);
/*
* External TPU interface
*/
int gsa_load_tpu_fw_image(struct device *gsa,
dma_addr_t img_meta,
phys_addr_t img_body)
{
return gsa_send_load_img_cmd(gsa, GSA_MB_CMD_LOAD_TPU_FW_IMG,
img_meta, img_body);
}
EXPORT_SYMBOL_GPL(gsa_load_tpu_fw_image);
int gsa_unload_tpu_fw_image(struct device *gsa)
{
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_tz_send_hwmgr_unload_fw_image_cmd(&s->tpu_srv);
}
EXPORT_SYMBOL_GPL(gsa_unload_tpu_fw_image);
int gsa_send_tpu_cmd(struct device *gsa, enum gsa_tpu_cmd arg)
{
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_tz_send_hwmgr_state_cmd(&s->tpu_srv, arg);
}
EXPORT_SYMBOL_GPL(gsa_send_tpu_cmd);
/*
* External DSP interface
*/
int gsa_load_dsp_fw_image(struct device *gsa,
dma_addr_t img_meta,
phys_addr_t img_body)
{
return gsa_send_load_img_cmd(gsa, GSA_MB_CMD_LOAD_DSP_FW_IMG,
img_meta, img_body);
}
EXPORT_SYMBOL_GPL(gsa_load_dsp_fw_image);
int gsa_unload_dsp_fw_image(struct device *gsa)
{
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_tz_send_hwmgr_unload_fw_image_cmd(&s->dsp_srv);
}
EXPORT_SYMBOL_GPL(gsa_unload_dsp_fw_image);
int gsa_send_dsp_cmd(struct device *gsa, enum gsa_dsp_cmd arg)
{
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
return gsa_tz_send_hwmgr_state_cmd(&s->dsp_srv, arg);
}
EXPORT_SYMBOL_GPL(gsa_send_dsp_cmd);
/*
* External KDN interface
*/
static int send_kdn_cmd(struct gsa_dev_state *s, u32 cmd,
void *dst_buf, size_t dst_buf_sz, u32 opts,
const void *src_data, size_t src_data_len)
{
int ret;
size_t cb;
u32 req[KDN_REQ_ARGC];
u32 rsp[KDN_RSP_ARGC];
if (dst_buf_sz) {
if (!dst_buf) {
/* invalid args */
return -EINVAL;
}
if (dst_buf_sz > s->bb_sz) {
/* too much data */
return -EINVAL;
}
}
/* copy in data */
if (src_data_len) {
if (!src_data) {
/* invalid args */
return -EINVAL;
}
if (src_data_len > s->bb_sz) {
/* too much data */
return -EINVAL;
}
memcpy(s->bb_va, src_data, src_data_len);
}
/* Invoke KDN command */
req[KDN_DATA_BUF_ADDR_LO_IDX] = (u32)s->bb_da;
req[KDN_DATA_BUF_ADDR_HI_IDX] = (u32)(s->bb_da >> 32);
req[KDN_DATA_BUF_SIZE_IDX] = max_t(u32, dst_buf_sz, src_data_len);
req[KDN_DATA_LEN_IDX] = (u32)src_data_len;
req[KDN_OPTION_IDX] = opts;
ret = gsa_send_mbox_cmd(s->mb, cmd, req, ARRAY_SIZE(req),
rsp, ARRAY_SIZE(rsp));
if (ret < 0) {
/* mailbox command failed */
return ret;
}
if (ret != KDN_RSP_ARGC) {
/* unexpected reply */
return -EINVAL;
}
/* copy data out */
cb = rsp[KDN_RSP_DATA_LEN_IDX];
if (cb > dst_buf_sz) {
/* buffer too short */
return -EINVAL;
}
if (cb) {
/* copy data to destination buffer */
memcpy(dst_buf, s->bb_va, cb);
}
return cb;
}
int gsa_kdn_derive_raw_secret(struct device *gsa, void *buf, size_t buf_sz,
const void *key_blob, size_t key_blob_len)
{
int ret;
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
mutex_lock(&s->bb_lock);
ret = send_kdn_cmd(s, GSA_MB_CMD_KDN_DERIVE_RAW_SECRET,
buf, buf_sz, 0, key_blob, key_blob_len);
mutex_unlock(&s->bb_lock);
return ret;
}
EXPORT_SYMBOL_GPL(gsa_kdn_derive_raw_secret);
int gsa_kdn_program_key(struct device *gsa, u32 slot, const void *key_blob,
size_t key_blob_len)
{
int ret;
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
mutex_lock(&s->bb_lock);
ret = send_kdn_cmd(s, GSA_MB_CMD_KDN_PROGRAM_KEY,
NULL, 0, slot, key_blob, key_blob_len);
mutex_unlock(&s->bb_lock);
return ret;
}
EXPORT_SYMBOL_GPL(gsa_kdn_program_key);
int gsa_kdn_restore_keys(struct device *gsa)
{
int ret;
/* Restore keys is a special no argument command */
ret = gsa_send_cmd(gsa, GSA_MB_CMD_KDN_RESTORE_KEYS, NULL, 0, NULL, 0);
if (ret < 0)
return ret;
else
return 0;
}
EXPORT_SYMBOL_GPL(gsa_kdn_restore_keys);
int gsa_kdn_set_operating_mode(struct device *gsa,
enum kdn_op_mode mode,
enum kdn_ufs_descr_type descr)
{
int ret;
u32 req[KDN_SET_OP_MODE_ARGC];
req[KDN_SET_OP_MODE_MODE_IDX] = mode;
req[KDN_SET_OP_MODE_UFS_DESCR_IDX] = descr;
ret = gsa_send_cmd(gsa, GSA_MB_CMD_KDN_SET_OP_MODE,
req, ARRAY_SIZE(req), NULL, 0);
if (ret < 0)
return ret;
else
return 0;
}
EXPORT_SYMBOL_GPL(gsa_kdn_set_operating_mode);
/*
* External SJTAG management interface
*/
static int send_sjtag_data_cmd(struct gsa_dev_state *s, u32 cmd,
void *dst_buf, size_t dst_buf_sz,
const void *src_data, size_t src_data_len,
u32 *status)
{
int ret;
size_t cb;
u32 req[SJTAG_DATA_REQ_ARGC];
u32 rsp[SJTAG_DATA_RSP_ARGC];
if (dst_buf_sz) {
if (!dst_buf) {
/* invalid args */
return -EINVAL;
}
if (dst_buf_sz > s->bb_sz) {
/* too much data */
return -EINVAL;
}
}
/* copy in data */
if (src_data_len) {
if (!src_data) {
/* invalid args */
return -EINVAL;
}
if (src_data_len > s->bb_sz) {
/* too much data */
return -EINVAL;
}
memcpy(s->bb_va, src_data, src_data_len);
}
/* Invoke SJTAG command */
req[SJTAG_DATA_BUF_ADDR_LO_IDX] = (u32)s->bb_da;
req[SJTAG_DATA_BUF_ADDR_HI_IDX] = (u32)(s->bb_da >> 32);
req[SJTAG_DATA_BUF_SIZE_IDX] = max_t(u32, dst_buf_sz, src_data_len);
req[SJTAG_DATA_LEN_IDX] = (u32)src_data_len;
ret = gsa_send_mbox_cmd(s->mb, cmd, req, ARRAY_SIZE(req),
rsp, ARRAY_SIZE(rsp));
if (ret < 0) {
/* mailbox command failed */
return ret;
}
if (ret != SJTAG_DATA_RSP_ARGC) {
/* unexpected reply */
return -EINVAL;
}
/* return command status */
if (status)
*status = rsp[SJTAG_DATA_RSP_STATUS_IDX];
/* copy data out */
cb = rsp[SJTAG_DATA_RSP_DATA_LEN_IDX];
if (cb > dst_buf_sz) {
/* buffer too short */
return -EINVAL;
}
if (cb) {
/* copy data to destination buffer */
memcpy(dst_buf, s->bb_va, cb);
}
return cb;
}
int gsa_sjtag_get_status(struct device *gsa, u32 *debug_allowed, u32 *hw_state,
u32 *debug_time)
{
int ret;
u32 rsp[SJTAG_STATUS_RSP_ARGC];
struct gsa_dev_state *s;
struct platform_device *pdev;
pdev = to_platform_device(gsa);
s = platform_get_drvdata(pdev);
ret = gsa_send_cmd(gsa, GSA_MB_CMD_SJTAG_GET_STATUS, NULL, 0,
rsp, ARRAY_SIZE(rsp));
if (ret < 0)
return ret;
/* we expect exactly 2 parameters */
if (ret != SJTAG_STATUS_RSP_ARGC)
return -EIO;
if (debug_allowed)
*debug_allowed= rsp[SJTAG_STATUS_RSP_DEBUG_ALLOWED_IDX];
if (hw_state)
*hw_state = rsp[SJTAG_STATUS_RSP_HW_STATUS_IDX];
if (debug_time)
*debug_time = rsp[SJTAG_STATUS_RSP_DEBUG_TIME_IDX];
return 0;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_get_status);
int gsa_sjtag_get_chip_id(struct device *gsa, u32 id[2])
{
int ret;
ret = gsa_send_cmd(gsa, GSA_MB_CMD_SJTAG_GET_CHIP_ID, NULL, 0, id, 2);
if (ret < 0)
return ret;
/* we expect exactly 2 parameters */
if (ret != 2)
return -EIO;
return 0;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_get_chip_id);
int gsa_sjtag_get_pub_key_hash(struct device *gsa, void *hash, size_t size,
u32 *status)
{
int ret;
struct gsa_dev_state *s;
struct platform_device *pdev;
pdev = to_platform_device(gsa);
s = platform_get_drvdata(pdev);
mutex_lock(&s->bb_lock);
ret = send_sjtag_data_cmd(s, GSA_MB_CMD_SJTAG_GET_PUB_KEY_HASH,
hash, size, NULL, 0, status);
mutex_unlock(&s->bb_lock);
return ret;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_get_pub_key_hash);
int gsa_sjtag_set_pub_key(struct device *gsa, const void *key, size_t size,
u32 *status)
{
int ret;
struct gsa_dev_state *s;
struct platform_device *pdev;
pdev = to_platform_device(gsa);
s = platform_get_drvdata(pdev);
mutex_lock(&s->bb_lock);
ret = send_sjtag_data_cmd(s, GSA_MB_CMD_SJTAG_SET_PUB_KEY,
NULL, 0, key, size, status);
mutex_unlock(&s->bb_lock);
return ret;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_set_pub_key);
int gsa_sjtag_get_challenge(struct device *gsa, void *challenge, size_t size,
u32 *status)
{
int ret;
struct gsa_dev_state *s;
struct platform_device *pdev;
pdev = to_platform_device(gsa);
s = platform_get_drvdata(pdev);
mutex_lock(&s->bb_lock);
ret = send_sjtag_data_cmd(s, GSA_MB_CMD_SJTAG_GET_CHALLENGE,
challenge, size, NULL, 0, status);
mutex_unlock(&s->bb_lock);
return ret;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_get_challenge);
int gsa_sjtag_send_srv_response(struct device *gsa,
const void *rsp, size_t size,
u32 *status)
{
int ret;
struct gsa_dev_state *s;
struct platform_device *pdev;
pdev = to_platform_device(gsa);
s = platform_get_drvdata(pdev);
mutex_lock(&s->bb_lock);
ret = send_sjtag_data_cmd(s, GSA_MB_CMD_SJTAG_ENABLE,
NULL, 0, rsp, size, status);
mutex_unlock(&s->bb_lock);
return ret;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_send_srv_response);
int gsa_sjtag_end_session(struct device *gsa, u32 *status)
{
int rc;
struct gsa_dev_state *s;
struct platform_device *pdev;
pdev = to_platform_device(gsa);
s = platform_get_drvdata(pdev);
rc = gsa_send_cmd(gsa, GSA_MB_CMD_SJTAG_FINISH, NULL, 0, status, 1);
if (rc < 0)
return rc;
/* exactly 1 argument is expected */
if (rc != 1)
return -EIO;
return 0;
}
EXPORT_SYMBOL_GPL(gsa_sjtag_end_session);
/********************************************************************/
static ssize_t gsa_log_show(struct device *gsa, struct device_attribute *attr, char *buf);
static DEVICE_ATTR(log_main, 0440, gsa_log_show, NULL);
static DEVICE_ATTR(log_intermediate, 0440, gsa_log_show, NULL);
static ssize_t gsa_log_show(struct device *gsa, struct device_attribute *attr, char *buf) {
struct platform_device *pdev = to_platform_device(gsa);
struct gsa_dev_state *s = platform_get_drvdata(pdev);
bool is_intermediate = (attr == &dev_attr_log_intermediate);
return gsa_log_read(s->log, is_intermediate, buf);
}
static struct attribute *gsa_attrs[] = {
&dev_attr_log_main.attr,
&dev_attr_log_intermediate.attr,
NULL,
};
ATTRIBUTE_GROUPS(gsa);
static int gsa_probe(struct platform_device *pdev)
{
int err;
struct gsa_dev_state *s;
struct device *dev = &pdev->dev;
s = devm_kzalloc(dev, sizeof(*s), GFP_KERNEL);
if (!s)
return -ENOMEM;
s->dev = dev;
mutex_init(&s->bb_lock);
platform_set_drvdata(pdev, s);
/*
* Set DMA mask and coherent to 36-bit as it is what GSA supports.
*/
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
if (err) {
dev_err(dev, "failed (%d) to setup dma mask\n", err);
return err;
}
/* initialize mailbox */
s->mb = gsa_mbox_init(pdev);
if (IS_ERR(s->mb))
return (int)PTR_ERR(s->mb);
/* add children */
err = devm_of_platform_populate(dev);
if (err < 0) {
dev_err(dev, "populate children failed (%d)\n", err);
return err;
}
/* alloc bounce buffer */
s->bb_va = dmam_alloc_coherent(dev, PAGE_SIZE, &s->bb_da, GFP_KERNEL);
if (!s->bb_va)
return -ENOMEM;
s->bb_sz = PAGE_SIZE;
/* Initialize TZ serice link to HWMGR */
gsa_tz_chan_ctx_init(&s->aoc_srv, HWMGR_AOC_PORT, dev);
gsa_tz_chan_ctx_init(&s->tpu_srv, HWMGR_TPU_PORT, dev);
gsa_tz_chan_ctx_init(&s->dsp_srv, HWMGR_DSP_PORT, dev);
/* Initialize log if configured */
s->log = gsa_log_init(pdev);
if (IS_ERR(s->log))
return PTR_ERR(s->log);
dev_info(dev, "Initialized\n");
return 0;
}
static int gsa_remove(struct platform_device *pdev)
{
struct gsa_dev_state *s = platform_get_drvdata(pdev);
/* close connection to tz services */
gsa_tz_chan_close(&s->aoc_srv);
gsa_tz_chan_close(&s->tpu_srv);
gsa_tz_chan_close(&s->dsp_srv);
return 0;
}
static const struct of_device_id gsa_of_match[] = {
{ .compatible = "google,gs101-gsa-v1", },
{},
};
MODULE_DEVICE_TABLE(of, gsa_of_match);
static struct platform_driver gsa_driver = {
.probe = gsa_probe,
.remove = gsa_remove,
.driver = {
.name = "gsa",
.of_match_table = gsa_of_match,
.dev_groups = gsa_groups,
},
};
static int __init gsa_driver_init(void)
{
return platform_driver_register(&gsa_driver);
}
static void __exit gsa_driver_exit(void)
{
platform_driver_unregister(&gsa_driver);
}
/* XXX - EPROBE_DEFER would be better. */
#if IS_ENABLED(CONFIG_GSA_PKVM)
MODULE_SOFTDEP("pre: pkvm-s2mpu");
#endif
MODULE_DESCRIPTION("Google GSA core platform driver");
MODULE_LICENSE("GPL v2");
module_init(gsa_driver_init);
module_exit(gsa_driver_exit);