fpga: Add scatterlist based programming

Requiring contiguous kernel memory is not a good idea, this is a limited
resource and allocation can fail under normal work loads.

This introduces a .write_sg op that supporting drivers can provide
to DMA directly from dis-contiguous memory and a new entry point
fpga_mgr_buf_load_sg that users can call to directly provide page
lists.

The full matrix of compatibility is provided, either the linear or sg
interface can be used by the user with a driver supporting either
interface.

A notable change for drivers is that the .write op can now be called
multiple times.

Signed-off-by: Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
Acked-by: Alan Tull <atull@opensource.altera.com>
Acked-by: Moritz Fischer <moritz.fischer@ettus.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Jason Gunthorpe
2017-02-01 12:48:44 -07:00
committed by Greg Kroah-Hartman
parent b496df86ac
commit baa6d39663
3 changed files with 230 additions and 36 deletions

View File

@@ -25,16 +25,106 @@
#include <linux/of.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/highmem.h>
static DEFINE_IDA(fpga_mgr_ida);
static struct class *fpga_mgr_class;
/*
* Call the low level driver's write_init function. This will do the
* device-specific things to get the FPGA into the state where it is ready to
* receive an FPGA image. The low level driver only gets to see the first
* initial_header_size bytes in the buffer.
*/
static int fpga_mgr_write_init_buf(struct fpga_manager *mgr,
struct fpga_image_info *info,
const char *buf, size_t count)
{
int ret;
mgr->state = FPGA_MGR_STATE_WRITE_INIT;
if (!mgr->mops->initial_header_size)
ret = mgr->mops->write_init(mgr, info, NULL, 0);
else
ret = mgr->mops->write_init(
mgr, info, buf, min(mgr->mops->initial_header_size, count));
if (ret) {
dev_err(&mgr->dev, "Error preparing FPGA for writing\n");
mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR;
return ret;
}
return 0;
}
static int fpga_mgr_write_init_sg(struct fpga_manager *mgr,
struct fpga_image_info *info,
struct sg_table *sgt)
{
struct sg_mapping_iter miter;
size_t len;
char *buf;
int ret;
if (!mgr->mops->initial_header_size)
return fpga_mgr_write_init_buf(mgr, info, NULL, 0);
/*
* First try to use miter to map the first fragment to access the
* header, this is the typical path.
*/
sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG);
if (sg_miter_next(&miter) &&
miter.length >= mgr->mops->initial_header_size) {
ret = fpga_mgr_write_init_buf(mgr, info, miter.addr,
miter.length);
sg_miter_stop(&miter);
return ret;
}
sg_miter_stop(&miter);
/* Otherwise copy the fragments into temporary memory. */
buf = kmalloc(mgr->mops->initial_header_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len = sg_copy_to_buffer(sgt->sgl, sgt->nents, buf,
mgr->mops->initial_header_size);
ret = fpga_mgr_write_init_buf(mgr, info, buf, len);
kfree(buf);
return ret;
}
/*
* After all the FPGA image has been written, do the device specific steps to
* finish and set the FPGA into operating mode.
*/
static int fpga_mgr_write_complete(struct fpga_manager *mgr,
struct fpga_image_info *info)
{
int ret;
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE;
ret = mgr->mops->write_complete(mgr, info);
if (ret) {
dev_err(&mgr->dev, "Error after writing image data to FPGA\n");
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR;
return ret;
}
mgr->state = FPGA_MGR_STATE_OPERATING;
return 0;
}
/**
* fpga_mgr_buf_load - load fpga from image in buffer
* fpga_mgr_buf_load_sg - load fpga from image in buffer from a scatter list
* @mgr: fpga manager
* @info: fpga image specific information
* @buf: buffer contain fpga image
* @count: byte count of buf
* @sgt: scatterlist table
*
* Step the low level fpga manager through the device-specific steps of getting
* an FPGA ready to be configured, writing the image to it, then doing whatever
@@ -42,54 +132,139 @@ static struct class *fpga_mgr_class;
* mgr pointer from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is
* not an error code.
*
* This is the preferred entry point for FPGA programming, it does not require
* any contiguous kernel memory.
*
* Return: 0 on success, negative error code otherwise.
*/
int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info,
const char *buf, size_t count)
int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, struct fpga_image_info *info,
struct sg_table *sgt)
{
struct device *dev = &mgr->dev;
int ret;
/*
* Call the low level driver's write_init function. This will do the
* device-specific things to get the FPGA into the state where it is
* ready to receive an FPGA image. The low level driver only gets to
* see the first initial_header_size bytes in the buffer.
*/
mgr->state = FPGA_MGR_STATE_WRITE_INIT;
ret = mgr->mops->write_init(mgr, info, buf,
min(mgr->mops->initial_header_size, count));
ret = fpga_mgr_write_init_sg(mgr, info, sgt);
if (ret)
return ret;
/* Write the FPGA image to the FPGA. */
mgr->state = FPGA_MGR_STATE_WRITE;
if (mgr->mops->write_sg) {
ret = mgr->mops->write_sg(mgr, sgt);
} else {
struct sg_mapping_iter miter;
sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG);
while (sg_miter_next(&miter)) {
ret = mgr->mops->write(mgr, miter.addr, miter.length);
if (ret)
break;
}
sg_miter_stop(&miter);
}
if (ret) {
dev_err(dev, "Error preparing FPGA for writing\n");
mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR;
dev_err(&mgr->dev, "Error while writing image data to FPGA\n");
mgr->state = FPGA_MGR_STATE_WRITE_ERR;
return ret;
}
return fpga_mgr_write_complete(mgr, info);
}
EXPORT_SYMBOL_GPL(fpga_mgr_buf_load_sg);
static int fpga_mgr_buf_load_mapped(struct fpga_manager *mgr,
struct fpga_image_info *info,
const char *buf, size_t count)
{
int ret;
ret = fpga_mgr_write_init_buf(mgr, info, buf, count);
if (ret)
return ret;
/*
* Write the FPGA image to the FPGA.
*/
mgr->state = FPGA_MGR_STATE_WRITE;
ret = mgr->mops->write(mgr, buf, count);
if (ret) {
dev_err(dev, "Error while writing image data to FPGA\n");
dev_err(&mgr->dev, "Error while writing image data to FPGA\n");
mgr->state = FPGA_MGR_STATE_WRITE_ERR;
return ret;
}
/*
* After all the FPGA image has been written, do the device specific
* steps to finish and set the FPGA into operating mode.
*/
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE;
ret = mgr->mops->write_complete(mgr, info);
if (ret) {
dev_err(dev, "Error after writing image data to FPGA\n");
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR;
return ret;
}
mgr->state = FPGA_MGR_STATE_OPERATING;
return fpga_mgr_write_complete(mgr, info);
}
return 0;
/**
* fpga_mgr_buf_load - load fpga from image in buffer
* @mgr: fpga manager
* @flags: flags setting fpga confuration modes
* @buf: buffer contain fpga image
* @count: byte count of buf
*
* Step the low level fpga manager through the device-specific steps of getting
* an FPGA ready to be configured, writing the image to it, then doing whatever
* post-configuration steps necessary. This code assumes the caller got the
* mgr pointer from of_fpga_mgr_get() and checked that it is not an error code.
*
* Return: 0 on success, negative error code otherwise.
*/
int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info,
const char *buf, size_t count)
{
struct page **pages;
struct sg_table sgt;
const void *p;
int nr_pages;
int index;
int rc;
/*
* This is just a fast path if the caller has already created a
* contiguous kernel buffer and the driver doesn't require SG, non-SG
* drivers will still work on the slow path.
*/
if (mgr->mops->write)
return fpga_mgr_buf_load_mapped(mgr, info, buf, count);
/*
* Convert the linear kernel pointer into a sg_table of pages for use
* by the driver.
*/
nr_pages = DIV_ROUND_UP((unsigned long)buf + count, PAGE_SIZE) -
(unsigned long)buf / PAGE_SIZE;
pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return -ENOMEM;
p = buf - offset_in_page(buf);
for (index = 0; index < nr_pages; index++) {
if (is_vmalloc_addr(p))
pages[index] = vmalloc_to_page(p);
else
pages[index] = kmap_to_page((void *)p);
if (!pages[index]) {
kfree(pages);
return -EFAULT;
}
p += PAGE_SIZE;
}
/*
* The temporary pages list is used to code share the merging algorithm
* in sg_alloc_table_from_pages
*/
rc = sg_alloc_table_from_pages(&sgt, pages, index, offset_in_page(buf),
count, GFP_KERNEL);
kfree(pages);
if (rc)
return rc;
rc = fpga_mgr_buf_load_sg(mgr, info, &sgt);
sg_free_table(&sgt);
return rc;
}
EXPORT_SYMBOL_GPL(fpga_mgr_buf_load);
@@ -291,8 +466,9 @@ int fpga_mgr_register(struct device *dev, const char *name,
struct fpga_manager *mgr;
int id, ret;
if (!mops || !mops->write_init || !mops->write ||
!mops->write_complete || !mops->state) {
if (!mops || !mops->write_complete || !mops->state ||
!mops->write_init || (!mops->write && !mops->write_sg) ||
(mops->write && mops->write_sg)) {
dev_err(dev, "Attempt to register without fpga_manager_ops\n");
return -EINVAL;
}