forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
12 KiB
516 lines
12 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* FPGA Manager Driver for Intel Stratix10 SoC |
|
* |
|
* Copyright (C) 2018 Intel Corporation |
|
*/ |
|
#include <linux/completion.h> |
|
#include <linux/fpga/fpga-mgr.h> |
|
#include <linux/firmware/intel/stratix10-svc-client.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/of_platform.h> |
|
|
|
/* |
|
* FPGA programming requires a higher level of privilege (EL3), per the SoC |
|
* design. |
|
*/ |
|
#define NUM_SVC_BUFS 4 |
|
#define SVC_BUF_SIZE SZ_512K |
|
|
|
/* Indicates buffer is in use if set */ |
|
#define SVC_BUF_LOCK 0 |
|
|
|
#define S10_BUFFER_TIMEOUT (msecs_to_jiffies(SVC_RECONFIG_BUFFER_TIMEOUT_MS)) |
|
#define S10_RECONFIG_TIMEOUT (msecs_to_jiffies(SVC_RECONFIG_REQUEST_TIMEOUT_MS)) |
|
|
|
/* |
|
* struct s10_svc_buf |
|
* buf: virtual address of buf provided by service layer |
|
* lock: locked if buffer is in use |
|
*/ |
|
struct s10_svc_buf { |
|
char *buf; |
|
unsigned long lock; |
|
}; |
|
|
|
struct s10_priv { |
|
struct stratix10_svc_chan *chan; |
|
struct stratix10_svc_client client; |
|
struct completion status_return_completion; |
|
struct s10_svc_buf svc_bufs[NUM_SVC_BUFS]; |
|
unsigned long status; |
|
}; |
|
|
|
static int s10_svc_send_msg(struct s10_priv *priv, |
|
enum stratix10_svc_command_code command, |
|
void *payload, u32 payload_length) |
|
{ |
|
struct stratix10_svc_chan *chan = priv->chan; |
|
struct device *dev = priv->client.dev; |
|
struct stratix10_svc_client_msg msg; |
|
int ret; |
|
|
|
dev_dbg(dev, "%s cmd=%d payload=%p length=%d\n", |
|
__func__, command, payload, payload_length); |
|
|
|
msg.command = command; |
|
msg.payload = payload; |
|
msg.payload_length = payload_length; |
|
|
|
ret = stratix10_svc_send(chan, &msg); |
|
dev_dbg(dev, "stratix10_svc_send returned status %d\n", ret); |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Free buffers allocated from the service layer's pool that are not in use. |
|
* Return true when all buffers are freed. |
|
*/ |
|
static bool s10_free_buffers(struct fpga_manager *mgr) |
|
{ |
|
struct s10_priv *priv = mgr->priv; |
|
uint num_free = 0; |
|
uint i; |
|
|
|
for (i = 0; i < NUM_SVC_BUFS; i++) { |
|
if (!priv->svc_bufs[i].buf) { |
|
num_free++; |
|
continue; |
|
} |
|
|
|
if (!test_and_set_bit_lock(SVC_BUF_LOCK, |
|
&priv->svc_bufs[i].lock)) { |
|
stratix10_svc_free_memory(priv->chan, |
|
priv->svc_bufs[i].buf); |
|
priv->svc_bufs[i].buf = NULL; |
|
num_free++; |
|
} |
|
} |
|
|
|
return num_free == NUM_SVC_BUFS; |
|
} |
|
|
|
/* |
|
* Returns count of how many buffers are not in use. |
|
*/ |
|
static uint s10_free_buffer_count(struct fpga_manager *mgr) |
|
{ |
|
struct s10_priv *priv = mgr->priv; |
|
uint num_free = 0; |
|
uint i; |
|
|
|
for (i = 0; i < NUM_SVC_BUFS; i++) |
|
if (!priv->svc_bufs[i].buf) |
|
num_free++; |
|
|
|
return num_free; |
|
} |
|
|
|
/* |
|
* s10_unlock_bufs |
|
* Given the returned buffer address, match that address to our buffer struct |
|
* and unlock that buffer. This marks it as available to be refilled and sent |
|
* (or freed). |
|
* priv: private data |
|
* kaddr: kernel address of buffer that was returned from service layer |
|
*/ |
|
static void s10_unlock_bufs(struct s10_priv *priv, void *kaddr) |
|
{ |
|
uint i; |
|
|
|
if (!kaddr) |
|
return; |
|
|
|
for (i = 0; i < NUM_SVC_BUFS; i++) |
|
if (priv->svc_bufs[i].buf == kaddr) { |
|
clear_bit_unlock(SVC_BUF_LOCK, |
|
&priv->svc_bufs[i].lock); |
|
return; |
|
} |
|
|
|
WARN(1, "Unknown buffer returned from service layer %p\n", kaddr); |
|
} |
|
|
|
/* |
|
* s10_receive_callback - callback for service layer to use to provide client |
|
* (this driver) messages received through the mailbox. |
|
* client: service layer client struct |
|
* data: message from service layer |
|
*/ |
|
static void s10_receive_callback(struct stratix10_svc_client *client, |
|
struct stratix10_svc_cb_data *data) |
|
{ |
|
struct s10_priv *priv = client->priv; |
|
u32 status; |
|
int i; |
|
|
|
WARN_ONCE(!data, "%s: stratix10_svc_rc_data = NULL", __func__); |
|
|
|
status = data->status; |
|
|
|
/* |
|
* Here we set status bits as we receive them. Elsewhere, we always use |
|
* test_and_clear_bit() to check status in priv->status |
|
*/ |
|
for (i = 0; i <= SVC_STATUS_ERROR; i++) |
|
if (status & (1 << i)) |
|
set_bit(i, &priv->status); |
|
|
|
if (status & BIT(SVC_STATUS_BUFFER_DONE)) { |
|
s10_unlock_bufs(priv, data->kaddr1); |
|
s10_unlock_bufs(priv, data->kaddr2); |
|
s10_unlock_bufs(priv, data->kaddr3); |
|
} |
|
|
|
complete(&priv->status_return_completion); |
|
} |
|
|
|
/* |
|
* s10_ops_write_init - prepare for FPGA reconfiguration by requesting |
|
* partial reconfig and allocating buffers from the service layer. |
|
*/ |
|
static int s10_ops_write_init(struct fpga_manager *mgr, |
|
struct fpga_image_info *info, |
|
const char *buf, size_t count) |
|
{ |
|
struct s10_priv *priv = mgr->priv; |
|
struct device *dev = priv->client.dev; |
|
struct stratix10_svc_command_config_type ctype; |
|
char *kbuf; |
|
uint i; |
|
int ret; |
|
|
|
ctype.flags = 0; |
|
if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { |
|
dev_dbg(dev, "Requesting partial reconfiguration.\n"); |
|
ctype.flags |= BIT(COMMAND_RECONFIG_FLAG_PARTIAL); |
|
} else { |
|
dev_dbg(dev, "Requesting full reconfiguration.\n"); |
|
} |
|
|
|
reinit_completion(&priv->status_return_completion); |
|
ret = s10_svc_send_msg(priv, COMMAND_RECONFIG, |
|
&ctype, sizeof(ctype)); |
|
if (ret < 0) |
|
goto init_done; |
|
|
|
ret = wait_for_completion_timeout( |
|
&priv->status_return_completion, S10_RECONFIG_TIMEOUT); |
|
if (!ret) { |
|
dev_err(dev, "timeout waiting for RECONFIG_REQUEST\n"); |
|
ret = -ETIMEDOUT; |
|
goto init_done; |
|
} |
|
|
|
ret = 0; |
|
if (!test_and_clear_bit(SVC_STATUS_OK, &priv->status)) { |
|
ret = -ETIMEDOUT; |
|
goto init_done; |
|
} |
|
|
|
/* Allocate buffers from the service layer's pool. */ |
|
for (i = 0; i < NUM_SVC_BUFS; i++) { |
|
kbuf = stratix10_svc_allocate_memory(priv->chan, SVC_BUF_SIZE); |
|
if (!kbuf) { |
|
s10_free_buffers(mgr); |
|
ret = -ENOMEM; |
|
goto init_done; |
|
} |
|
|
|
priv->svc_bufs[i].buf = kbuf; |
|
priv->svc_bufs[i].lock = 0; |
|
} |
|
|
|
init_done: |
|
stratix10_svc_done(priv->chan); |
|
return ret; |
|
} |
|
|
|
/* |
|
* s10_send_buf - send a buffer to the service layer queue |
|
* mgr: fpga manager struct |
|
* buf: fpga image buffer |
|
* count: size of buf in bytes |
|
* Returns # of bytes transferred or -ENOBUFS if the all the buffers are in use |
|
* or if the service queue is full. Never returns 0. |
|
*/ |
|
static int s10_send_buf(struct fpga_manager *mgr, const char *buf, size_t count) |
|
{ |
|
struct s10_priv *priv = mgr->priv; |
|
struct device *dev = priv->client.dev; |
|
void *svc_buf; |
|
size_t xfer_sz; |
|
int ret; |
|
uint i; |
|
|
|
/* get/lock a buffer that that's not being used */ |
|
for (i = 0; i < NUM_SVC_BUFS; i++) |
|
if (!test_and_set_bit_lock(SVC_BUF_LOCK, |
|
&priv->svc_bufs[i].lock)) |
|
break; |
|
|
|
if (i == NUM_SVC_BUFS) |
|
return -ENOBUFS; |
|
|
|
xfer_sz = count < SVC_BUF_SIZE ? count : SVC_BUF_SIZE; |
|
|
|
svc_buf = priv->svc_bufs[i].buf; |
|
memcpy(svc_buf, buf, xfer_sz); |
|
ret = s10_svc_send_msg(priv, COMMAND_RECONFIG_DATA_SUBMIT, |
|
svc_buf, xfer_sz); |
|
if (ret < 0) { |
|
dev_err(dev, |
|
"Error while sending data to service layer (%d)", ret); |
|
clear_bit_unlock(SVC_BUF_LOCK, &priv->svc_bufs[i].lock); |
|
return ret; |
|
} |
|
|
|
return xfer_sz; |
|
} |
|
|
|
/* |
|
* Send an FPGA image to privileged layers to write to the FPGA. When done |
|
* sending, free all service layer buffers we allocated in write_init. |
|
*/ |
|
static int s10_ops_write(struct fpga_manager *mgr, const char *buf, |
|
size_t count) |
|
{ |
|
struct s10_priv *priv = mgr->priv; |
|
struct device *dev = priv->client.dev; |
|
long wait_status; |
|
int sent = 0; |
|
int ret = 0; |
|
|
|
/* |
|
* Loop waiting for buffers to be returned. When a buffer is returned, |
|
* reuse it to send more data or free if if all data has been sent. |
|
*/ |
|
while (count > 0 || s10_free_buffer_count(mgr) != NUM_SVC_BUFS) { |
|
reinit_completion(&priv->status_return_completion); |
|
|
|
if (count > 0) { |
|
sent = s10_send_buf(mgr, buf, count); |
|
if (sent < 0) |
|
continue; |
|
|
|
count -= sent; |
|
buf += sent; |
|
} else { |
|
if (s10_free_buffers(mgr)) |
|
return 0; |
|
|
|
ret = s10_svc_send_msg( |
|
priv, COMMAND_RECONFIG_DATA_CLAIM, |
|
NULL, 0); |
|
if (ret < 0) |
|
break; |
|
} |
|
|
|
/* |
|
* If callback hasn't already happened, wait for buffers to be |
|
* returned from service layer |
|
*/ |
|
wait_status = 1; /* not timed out */ |
|
if (!priv->status) |
|
wait_status = wait_for_completion_timeout( |
|
&priv->status_return_completion, |
|
S10_BUFFER_TIMEOUT); |
|
|
|
if (test_and_clear_bit(SVC_STATUS_BUFFER_DONE, &priv->status) || |
|
test_and_clear_bit(SVC_STATUS_BUFFER_SUBMITTED, |
|
&priv->status)) { |
|
ret = 0; |
|
continue; |
|
} |
|
|
|
if (test_and_clear_bit(SVC_STATUS_ERROR, &priv->status)) { |
|
dev_err(dev, "ERROR - giving up - SVC_STATUS_ERROR\n"); |
|
ret = -EFAULT; |
|
break; |
|
} |
|
|
|
if (!wait_status) { |
|
dev_err(dev, "timeout waiting for svc layer buffers\n"); |
|
ret = -ETIMEDOUT; |
|
break; |
|
} |
|
} |
|
|
|
if (!s10_free_buffers(mgr)) |
|
dev_err(dev, "%s not all buffers were freed\n", __func__); |
|
|
|
return ret; |
|
} |
|
|
|
static int s10_ops_write_complete(struct fpga_manager *mgr, |
|
struct fpga_image_info *info) |
|
{ |
|
struct s10_priv *priv = mgr->priv; |
|
struct device *dev = priv->client.dev; |
|
unsigned long timeout; |
|
int ret; |
|
|
|
timeout = usecs_to_jiffies(info->config_complete_timeout_us); |
|
|
|
do { |
|
reinit_completion(&priv->status_return_completion); |
|
|
|
ret = s10_svc_send_msg(priv, COMMAND_RECONFIG_STATUS, NULL, 0); |
|
if (ret < 0) |
|
break; |
|
|
|
ret = wait_for_completion_timeout( |
|
&priv->status_return_completion, timeout); |
|
if (!ret) { |
|
dev_err(dev, |
|
"timeout waiting for RECONFIG_COMPLETED\n"); |
|
ret = -ETIMEDOUT; |
|
break; |
|
} |
|
/* Not error or timeout, so ret is # of jiffies until timeout */ |
|
timeout = ret; |
|
ret = 0; |
|
|
|
if (test_and_clear_bit(SVC_STATUS_COMPLETED, &priv->status)) |
|
break; |
|
|
|
if (test_and_clear_bit(SVC_STATUS_ERROR, &priv->status)) { |
|
dev_err(dev, "ERROR - giving up - SVC_STATUS_ERROR\n"); |
|
ret = -EFAULT; |
|
break; |
|
} |
|
} while (1); |
|
|
|
stratix10_svc_done(priv->chan); |
|
|
|
return ret; |
|
} |
|
|
|
static enum fpga_mgr_states s10_ops_state(struct fpga_manager *mgr) |
|
{ |
|
return FPGA_MGR_STATE_UNKNOWN; |
|
} |
|
|
|
static const struct fpga_manager_ops s10_ops = { |
|
.state = s10_ops_state, |
|
.write_init = s10_ops_write_init, |
|
.write = s10_ops_write, |
|
.write_complete = s10_ops_write_complete, |
|
}; |
|
|
|
static int s10_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct s10_priv *priv; |
|
struct fpga_manager *mgr; |
|
int ret; |
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
priv->client.dev = dev; |
|
priv->client.receive_cb = s10_receive_callback; |
|
priv->client.priv = priv; |
|
|
|
priv->chan = stratix10_svc_request_channel_byname(&priv->client, |
|
SVC_CLIENT_FPGA); |
|
if (IS_ERR(priv->chan)) { |
|
dev_err(dev, "couldn't get service channel (%s)\n", |
|
SVC_CLIENT_FPGA); |
|
return PTR_ERR(priv->chan); |
|
} |
|
|
|
init_completion(&priv->status_return_completion); |
|
|
|
mgr = fpga_mgr_create(dev, "Stratix10 SOC FPGA Manager", |
|
&s10_ops, priv); |
|
if (!mgr) { |
|
dev_err(dev, "unable to create FPGA manager\n"); |
|
ret = -ENOMEM; |
|
goto probe_err; |
|
} |
|
|
|
ret = fpga_mgr_register(mgr); |
|
if (ret) { |
|
dev_err(dev, "unable to register FPGA manager\n"); |
|
fpga_mgr_free(mgr); |
|
goto probe_err; |
|
} |
|
|
|
platform_set_drvdata(pdev, mgr); |
|
return ret; |
|
|
|
probe_err: |
|
stratix10_svc_free_channel(priv->chan); |
|
return ret; |
|
} |
|
|
|
static int s10_remove(struct platform_device *pdev) |
|
{ |
|
struct fpga_manager *mgr = platform_get_drvdata(pdev); |
|
struct s10_priv *priv = mgr->priv; |
|
|
|
fpga_mgr_unregister(mgr); |
|
fpga_mgr_free(mgr); |
|
stratix10_svc_free_channel(priv->chan); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct of_device_id s10_of_match[] = { |
|
{.compatible = "intel,stratix10-soc-fpga-mgr"}, |
|
{.compatible = "intel,agilex-soc-fpga-mgr"}, |
|
{}, |
|
}; |
|
|
|
MODULE_DEVICE_TABLE(of, s10_of_match); |
|
|
|
static struct platform_driver s10_driver = { |
|
.probe = s10_probe, |
|
.remove = s10_remove, |
|
.driver = { |
|
.name = "Stratix10 SoC FPGA manager", |
|
.of_match_table = of_match_ptr(s10_of_match), |
|
}, |
|
}; |
|
|
|
static int __init s10_init(void) |
|
{ |
|
struct device_node *fw_np; |
|
struct device_node *np; |
|
int ret; |
|
|
|
fw_np = of_find_node_by_name(NULL, "svc"); |
|
if (!fw_np) |
|
return -ENODEV; |
|
|
|
of_node_get(fw_np); |
|
np = of_find_matching_node(fw_np, s10_of_match); |
|
if (!np) { |
|
of_node_put(fw_np); |
|
return -ENODEV; |
|
} |
|
|
|
of_node_put(np); |
|
ret = of_platform_populate(fw_np, s10_of_match, NULL, NULL); |
|
of_node_put(fw_np); |
|
if (ret) |
|
return ret; |
|
|
|
return platform_driver_register(&s10_driver); |
|
} |
|
|
|
static void __exit s10_exit(void) |
|
{ |
|
return platform_driver_unregister(&s10_driver); |
|
} |
|
|
|
module_init(s10_init); |
|
module_exit(s10_exit); |
|
|
|
MODULE_AUTHOR("Alan Tull <[email protected]>"); |
|
MODULE_DESCRIPTION("Intel Stratix 10 SOC FPGA Manager"); |
|
MODULE_LICENSE("GPL v2");
|
|
|