Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Multiple changes #32

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The driver requires a node in the device tree. This node describes the DMA chann
* `compatible` - This must be the string "xlnx,axidma-chrdev". This is used to match the driver with the device tree node.
* `dmas` - A list of phandles (references to other device tree nodes) of Xilinx AXI DMA or VDMA device tree nodes, followed by either 0 or 1. This refers to the child node inside of the Xilinx AXI DMA/VDMA device tree node, 0 of course being the first child node.
* `dma-names` - A list of names for the DMA channels. The names can be completely arbitrary, but they must be unique. This is required by the DMA interface function `dma_request_slave_channel()`, but is otherwise unused by the driver. In the future, the driver will use the names in printed messages.
* `index` - A non-negative integer which represents the index of this DMA device, optional if only one DMA device is present. If unset, defaults to 0. The indexes are not required to be consecutive, but they must be unique. The corresponding character device will appear as `/dev/axidma` when index is 0 or `/dev/axidma<index>` otherwise.

For the Xilinx AXI DMA/VDMA device tree nodes, the only requirement is that the `device-id` property is unique, but they can be completely arbitrary. This is how the channels are referred to in both the driver and from userspace. For more information on creating AXI DMA/VDMA device tree nodes, consult the kernel [documentation](https://github.com/Xilinx/linux-xlnx/blob/master/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt) for them.

Expand Down
29 changes: 27 additions & 2 deletions driver/axi_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,34 @@ static int axidma_probe(struct platform_device *pdev)
// Initialize the DMA interface
rc = axidma_dma_init(pdev, axidma_dev);
if (rc < 0) {
rc = -ENOSYS;
goto free_axidma_dev;
}

// Assign the character device name, minor number, and number of devices
axidma_dev->chrdev_name = chrdev_name;
axidma_dev->minor_num = minor_num;
axidma_dev->num_devices = NUM_DEVICES;

if (axidma_dev->chrdev_index > 0) {
rc = strlen(chrdev_name) + 10;
axidma_dev->chrdev_name = kmalloc(rc * sizeof(char), GFP_KERNEL);

if (axidma_dev->chrdev_name == NULL) {
axidma_err("Unable to allocate the AXI DMA chardev name string.\n");
rc = -ENOMEM;
goto free_axidma_dev;
}

snprintf(axidma_dev->chrdev_name, rc, "%s%d", chrdev_name,
axidma_dev->chrdev_index);
}
else
axidma_dev->chrdev_name = chrdev_name;

// Initialize the character device for the module.
rc = axidma_chrdev_init(axidma_dev);
if (rc < 0) {
rc = -ENOSYS;
goto destroy_dma_dev;
}

Expand All @@ -71,9 +88,11 @@ static int axidma_probe(struct platform_device *pdev)

destroy_dma_dev:
axidma_dma_exit(axidma_dev);
if (axidma_dev->chrdev_index > 0)
kfree(axidma_dev->chrdev_name);
free_axidma_dev:
kfree(axidma_dev);
return -ENOSYS;
return rc;
}

static int axidma_remove(struct platform_device *pdev)
Expand All @@ -90,7 +109,11 @@ static int axidma_remove(struct platform_device *pdev)
axidma_dma_exit(axidma_dev);

// Free the device structure
if (axidma_dev->chrdev_index > 0)
kfree(axidma_dev->chrdev_name);

kfree(axidma_dev);

return 0;
}

Expand All @@ -99,6 +122,8 @@ static const struct of_device_id axidma_compatible_of_ids[] = {
{}
};

MODULE_DEVICE_TABLE(of, axidma_compatible_of_ids);

static struct platform_driver axidma_driver = {
.driver = {
.name = MODULE_NAME,
Expand Down
5 changes: 4 additions & 1 deletion driver/axidma.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct axidma_device {
unsigned int minor_num; // The minor number of the device
dev_t dev_num; // The device number of the device
char *chrdev_name; // The name of the character device
int chrdev_index; // The optional character device index
struct device *device; // Device structure for the char device
struct class *dev_class; // The device class for the chardevice
struct cdev chrdev; // The character device structure
Expand All @@ -61,6 +62,7 @@ struct axidma_device {
int num_vdma_rx_chans; // The number of receive VDMA channels
int num_chans; // The total number of DMA channels
int notify_signal; // Signal used to notify transfer completion
void *user_data; // User data to be passed in the callback
struct platform_device *pdev; // The platofrm device from the device tree
struct axidma_cb_data *cb_data; // The callback data for each channel
struct axidma_chan *channels; // All available channels
Expand Down Expand Up @@ -98,7 +100,7 @@ void axidma_get_num_channels(struct axidma_device *dev,
struct axidma_num_channels *num_chans);
void axidma_get_channel_info(struct axidma_device *dev,
struct axidma_channel_info *chan_info);
int axidma_set_signal(struct axidma_device *dev, int signal);
int axidma_set_signal(struct axidma_device *dev, int signal, void *user_data);
int axidma_read_transfer(struct axidma_device *dev,
struct axidma_transaction *trans);
int axidma_write_transfer(struct axidma_device *dev,
Expand All @@ -108,6 +110,7 @@ int axidma_rw_transfer(struct axidma_device *dev,
int axidma_video_transfer(struct axidma_device *dev,
struct axidma_video_transaction *trans,
enum axidma_dir dir);
int axidma_get_residue(struct axidma_device *dev, struct axidma_residue *res);
int axidma_stop_channel(struct axidma_device *dev, struct axidma_chan *chan);
dma_addr_t axidma_uservirt_to_dma(struct axidma_device *dev, void *user_addr,
size_t size);
Expand Down
38 changes: 27 additions & 11 deletions driver/axidma_chrdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,14 @@
* Internal Definitions
*----------------------------------------------------------------------------*/

// TODO: Maybe this can be improved?
static struct axidma_device *axidma_dev;

// A structure that represents a DMA buffer allocation
struct axidma_dma_allocation {
size_t size; // Size of the buffer
void *user_addr; // User virtual address of the buffer
void *kern_addr; // Kernel virtual address of the buffer
dma_addr_t dma_addr; // DMA bus address of the buffer
struct list_head list; // List node pointers for allocation list
struct device *device; // Device structure for the char device
};

/* A structure that represents a DMA buffer allocation imported from another
Expand Down Expand Up @@ -202,13 +200,11 @@ static int axidma_put_external(struct axidma_device *dev, void *user_addr)

static void axidma_vma_close(struct vm_area_struct *vma)
{
struct axidma_device *dev;
struct axidma_dma_allocation *dma_alloc;

// Get the AXI DMA allocation data and free the DMA buffer
dev = axidma_dev;
dma_alloc = vma->vm_private_data;
dma_free_coherent(dev->device, dma_alloc->size, dma_alloc->kern_addr,
dma_free_coherent(dma_alloc->device, dma_alloc->size, dma_alloc->kern_addr,
dma_alloc->dma_addr);

// Remove the allocation from the list, and free the structure
Expand Down Expand Up @@ -239,7 +235,8 @@ static int axidma_open(struct inode *inode, struct file *file)
}

// Place the axidma structure in the private data of the file
file->private_data = (void *)axidma_dev;
file->private_data = container_of(inode->i_cdev, struct axidma_device,
chrdev);
return 0;
}

Expand Down Expand Up @@ -269,6 +266,7 @@ static int axidma_mmap(struct file *file, struct vm_area_struct *vma)
// Set the user virtual address and the size
dma_alloc->size = vma->vm_end - vma->vm_start;
dma_alloc->user_addr = (void *)vma->vm_start;
dma_alloc->device = dev->device;

// Configure the DMA device
of_dma_configure(dev->device, NULL);
Expand Down Expand Up @@ -340,10 +338,12 @@ static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
struct axidma_device *dev;
struct axidma_num_channels num_chans;
struct axidma_channel_info usr_chans, kern_chans;
struct axidma_signal_info sig_info;
struct axidma_register_buffer ext_buf;
struct axidma_transaction trans;
struct axidma_inout_transaction inout_trans;
struct axidma_video_transaction video_trans;
struct axidma_residue residue;
struct axidma_chan chan_info;

// Coerce the arguement as a userspace pointer
Expand Down Expand Up @@ -405,7 +405,12 @@ static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
break;

case AXIDMA_SET_DMA_SIGNAL:
rc = axidma_set_signal(dev, arg);
if (copy_from_user(&sig_info, arg_ptr, sizeof(sig_info)) != 0) {
axidma_err("Unable to copy signal info from userspace for "
"AXIDMA_SET_DMA_SIGNAL.\n");
return -EFAULT;
}
rc = axidma_set_signal(dev, sig_info.signal, sig_info.user_data);
break;

case AXIDMA_REGISTER_BUFFER:
Expand Down Expand Up @@ -486,6 +491,20 @@ static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
rc = axidma_video_transfer(dev, &video_trans, AXIDMA_WRITE);
break;

case AXIDMA_DMA_RESIDUE:
if (copy_from_user(&residue, arg_ptr, sizeof(residue)) != 0) {
axidma_err("Unable to copy residue info from userspace for "
"AXIDMA_DMA_RESIDUE.\n");
return -EFAULT;
}
rc = axidma_get_residue(dev, &residue);
if (copy_to_user(arg_ptr, &residue, sizeof(residue)) != 0) {
axidma_err("Unable to copy residue info to userspace for "
"AXIDMA_DMA_RESIDUE.\n");
return -EFAULT;
}
break;

case AXIDMA_STOP_DMA_CHANNEL:
if (copy_from_user(&chan_info, arg_ptr, sizeof(chan_info)) != 0) {
axidma_err("Unable to channel info from userspace for "
Expand Down Expand Up @@ -523,9 +542,6 @@ int axidma_chrdev_init(struct axidma_device *dev)
{
int rc;

// Store a global pointer to the axidma device
axidma_dev = dev;

// Allocate a major and minor number region for the character device
rc = alloc_chrdev_region(&dev->dev_num, dev->minor_num, dev->num_devices,
dev->chrdev_name);
Expand Down
33 changes: 31 additions & 2 deletions driver/axidma_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct axidma_transfer {
enum axidma_type type; // The type of the transfer (VDMA/DMA)
int channel_id; // The ID of the channel
int notify_signal; // The signal to use for async transfers
void *user_data; // User data to be passed in the callback
struct task_struct *process; // The process requesting the transfer
struct axidma_cb_data *cb_data; // The callback data struct

Expand All @@ -68,6 +69,7 @@ struct axidma_transfer {
struct axidma_cb_data {
int channel_id; // The id of the channel used
int notify_signal; // For async, signal to send
void *user_data; // User data to be passed in the callback
struct task_struct *process; // The process to send the signal to
struct completion *comp; // For sync, the notification to kernel
};
Expand Down Expand Up @@ -151,7 +153,8 @@ static void axidma_dma_callback(void *data)
memset(&sig_info, 0, sizeof(sig_info));
sig_info.si_signo = cb_data->notify_signal;
sig_info.si_code = SI_QUEUE;
sig_info.si_int = cb_data->channel_id;
sig_info.si_errno = cb_data->channel_id; //This shouldn't be here
sig_info.si_ptr = cb_data->user_data;
send_sig_info(cb_data->notify_signal, &sig_info, cb_data->process);
}
}
Expand Down Expand Up @@ -238,16 +241,19 @@ static int axidma_prep_transfer(struct axidma_chan *axidma_chan,
/* If we're going to wait for this channel, initialize the completion for
* the channel, and setup the callback to complete it. */
cb_data->channel_id = dma_tfr->channel_id;
cb_data->user_data = dma_tfr->user_data;
if (dma_tfr->wait) {
cb_data->comp = dma_comp;
cb_data->notify_signal = -1;
cb_data->user_data = dma_tfr->user_data;
cb_data->process = NULL;
init_completion(cb_data->comp);
dma_txnd->callback_param = cb_data;
dma_txnd->callback = axidma_dma_callback;
} else {
cb_data->comp = NULL;
cb_data->notify_signal = dma_tfr->notify_signal;
cb_data->user_data = dma_tfr->user_data;
cb_data->process = dma_tfr->process;
dma_txnd->callback_param = cb_data;
dma_txnd->callback = axidma_dma_callback;
Expand Down Expand Up @@ -335,7 +341,7 @@ void axidma_get_channel_info(struct axidma_device *dev,
return;
}

int axidma_set_signal(struct axidma_device *dev, int signal)
int axidma_set_signal(struct axidma_device *dev, int signal, void *user_data)
{
// Verify the signal is a real-time one
if (!VALID_NOTIFY_SIGNAL(signal)) {
Expand All @@ -346,6 +352,7 @@ int axidma_set_signal(struct axidma_device *dev, int signal)
}

dev->notify_signal = signal;
dev->user_data = user_data;
return 0;
}

Expand Down Expand Up @@ -381,6 +388,7 @@ int axidma_read_transfer(struct axidma_device *dev,
rx_tfr.wait = trans->wait;
rx_tfr.channel_id = trans->channel_id;
rx_tfr.notify_signal = dev->notify_signal;
rx_tfr.user_data = dev->user_data;
rx_tfr.process = get_current();
rx_tfr.cb_data = &dev->cb_data[trans->channel_id];

Expand Down Expand Up @@ -431,6 +439,7 @@ int axidma_write_transfer(struct axidma_device *dev,
tx_tfr.wait = trans->wait;
tx_tfr.channel_id = trans->channel_id;
tx_tfr.notify_signal = dev->notify_signal;
tx_tfr.user_data = dev->user_data;
tx_tfr.process = get_current();
tx_tfr.cb_data = &dev->cb_data[trans->channel_id];

Expand Down Expand Up @@ -496,6 +505,7 @@ int axidma_rw_transfer(struct axidma_device *dev,
tx_tfr.wait = false,
tx_tfr.channel_id = trans->tx_channel_id,
tx_tfr.notify_signal = dev->notify_signal,
tx_tfr.user_data = dev->user_data;
tx_tfr.process = get_current(),
tx_tfr.cb_data = &dev->cb_data[trans->tx_channel_id];
// FIXME: FIXME: FIXME: Temporary
Expand All @@ -510,6 +520,7 @@ int axidma_rw_transfer(struct axidma_device *dev,
rx_tfr.wait = trans->wait,
rx_tfr.channel_id = trans->rx_channel_id,
rx_tfr.notify_signal = dev->notify_signal,
rx_tfr.user_data = dev->user_data;
rx_tfr.process = get_current(),
rx_tfr.cb_data = &dev->cb_data[trans->rx_channel_id];
rx_tfr.vdma_tfr.height = 1080;
Expand Down Expand Up @@ -556,6 +567,7 @@ int axidma_video_transfer(struct axidma_device *dev,
.wait = false,
.channel_id = trans->channel_id,
.notify_signal = dev->notify_signal,
.user_data = dev->user_data,
.process = get_current(),
.vdma_tfr.width = trans->width,
.vdma_tfr.height = trans->height,
Expand Down Expand Up @@ -607,6 +619,23 @@ int axidma_video_transfer(struct axidma_device *dev,
return 0;
}

int axidma_get_residue(struct axidma_device *dev, struct axidma_residue *res)
{
struct axidma_chan *chan;
struct dma_tx_state state;

chan = axidma_get_chan(dev, res->channel_id);

if (chan != NULL) {
dmaengine_tx_status(chan->chan, chan->chan->cookie, &state);
res->residue = state.residue;

return 0;
}
else
return -ENODEV;
}

int axidma_stop_channel(struct axidma_device *dev,
struct axidma_chan *chan_info)
{
Expand Down
8 changes: 8 additions & 0 deletions driver/axidma_of.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,14 @@ int axidma_of_parse_dma_nodes(struct platform_device *pdev,
dev->num_vdma_tx_chans = 0;
dev->num_vdma_rx_chans = 0;

rc = of_property_read_u32(driver_node, "index", &dev->chrdev_index);

if (rc < 0) {
if (rc != -EINVAL)
axidma_node_err(driver_node, "Invalid index property, ignoring.\n");
dev->chrdev_index = -1;
}

/* For each DMA channel specified in the deivce tree, parse out the
* information about the channel, namely its direction and type. */
for (i = 0; i < dev->num_chans; i++)
Expand Down
Loading