![Christian Gromm](/assets/img/avatar_default.png)
On arm64/aarch64 architectures the allocation of coherent memory needs a device that has the dma_ops properly set. That's why the core module of the MOST driver is no longer able to allocate this type or memory. This patch moves the allocation process down to the interface drivers where the proper devices exist (e.g. platform device or USB system software). Signed-off-by: Christian Gromm <christian.gromm@microchip.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1629 lines
38 KiB
C
1629 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* core.c - Implementation of core module of MOST Linux driver stack
|
|
*
|
|
* Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/list.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/idr.h>
|
|
#include <most/core.h>
|
|
|
|
#define MAX_CHANNELS 64
|
|
#define STRING_SIZE 80
|
|
|
|
static struct ida mdev_id;
|
|
static int dummy_num_buffers;
|
|
|
|
static struct mostcore {
|
|
struct device dev;
|
|
struct device_driver drv;
|
|
struct bus_type bus;
|
|
struct list_head comp_list;
|
|
} mc;
|
|
|
|
#define to_driver(d) container_of(d, struct mostcore, drv)
|
|
|
|
struct pipe {
|
|
struct core_component *comp;
|
|
int refs;
|
|
int num_buffers;
|
|
};
|
|
|
|
struct most_channel {
|
|
struct device dev;
|
|
struct completion cleanup;
|
|
atomic_t mbo_ref;
|
|
atomic_t mbo_nq_level;
|
|
u16 channel_id;
|
|
char name[STRING_SIZE];
|
|
bool is_poisoned;
|
|
struct mutex start_mutex;
|
|
struct mutex nq_mutex; /* nq thread synchronization */
|
|
int is_starving;
|
|
struct most_interface *iface;
|
|
struct most_channel_config cfg;
|
|
bool keep_mbo;
|
|
bool enqueue_halt;
|
|
struct list_head fifo;
|
|
spinlock_t fifo_lock;
|
|
struct list_head halt_fifo;
|
|
struct list_head list;
|
|
struct pipe pipe0;
|
|
struct pipe pipe1;
|
|
struct list_head trash_fifo;
|
|
struct task_struct *hdm_enqueue_task;
|
|
wait_queue_head_t hdm_fifo_wq;
|
|
|
|
};
|
|
|
|
#define to_channel(d) container_of(d, struct most_channel, dev)
|
|
|
|
struct interface_private {
|
|
int dev_id;
|
|
char name[STRING_SIZE];
|
|
struct most_channel *channel[MAX_CHANNELS];
|
|
struct list_head channel_list;
|
|
};
|
|
|
|
static const struct {
|
|
int most_ch_data_type;
|
|
const char *name;
|
|
} ch_data_type[] = {
|
|
{ MOST_CH_CONTROL, "control\n" },
|
|
{ MOST_CH_ASYNC, "async\n" },
|
|
{ MOST_CH_SYNC, "sync\n" },
|
|
{ MOST_CH_ISOC, "isoc\n"},
|
|
{ MOST_CH_ISOC, "isoc_avp\n"},
|
|
};
|
|
|
|
/**
|
|
* list_pop_mbo - retrieves the first MBO of the list and removes it
|
|
* @ptr: the list head to grab the MBO from.
|
|
*/
|
|
#define list_pop_mbo(ptr) \
|
|
({ \
|
|
struct mbo *_mbo = list_first_entry(ptr, struct mbo, list); \
|
|
list_del(&_mbo->list); \
|
|
_mbo; \
|
|
})
|
|
|
|
/**
|
|
* most_free_mbo_coherent - free an MBO and its coherent buffer
|
|
* @mbo: most buffer
|
|
*/
|
|
static void most_free_mbo_coherent(struct mbo *mbo)
|
|
{
|
|
struct most_channel *c = mbo->context;
|
|
u16 const coherent_buf_size = c->cfg.buffer_size + c->cfg.extra_len;
|
|
|
|
if (c->iface->dma_free)
|
|
c->iface->dma_free(mbo, coherent_buf_size);
|
|
else
|
|
kfree(mbo->virt_address);
|
|
kfree(mbo);
|
|
if (atomic_sub_and_test(1, &c->mbo_ref))
|
|
complete(&c->cleanup);
|
|
}
|
|
|
|
/**
|
|
* flush_channel_fifos - clear the channel fifos
|
|
* @c: pointer to channel object
|
|
*/
|
|
static void flush_channel_fifos(struct most_channel *c)
|
|
{
|
|
unsigned long flags, hf_flags;
|
|
struct mbo *mbo, *tmp;
|
|
|
|
if (list_empty(&c->fifo) && list_empty(&c->halt_fifo))
|
|
return;
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
list_for_each_entry_safe(mbo, tmp, &c->fifo, list) {
|
|
list_del(&mbo->list);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
most_free_mbo_coherent(mbo);
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, hf_flags);
|
|
list_for_each_entry_safe(mbo, tmp, &c->halt_fifo, list) {
|
|
list_del(&mbo->list);
|
|
spin_unlock_irqrestore(&c->fifo_lock, hf_flags);
|
|
most_free_mbo_coherent(mbo);
|
|
spin_lock_irqsave(&c->fifo_lock, hf_flags);
|
|
}
|
|
spin_unlock_irqrestore(&c->fifo_lock, hf_flags);
|
|
|
|
if (unlikely((!list_empty(&c->fifo) || !list_empty(&c->halt_fifo))))
|
|
pr_info("WARN: fifo | trash fifo not empty\n");
|
|
}
|
|
|
|
/**
|
|
* flush_trash_fifo - clear the trash fifo
|
|
* @c: pointer to channel object
|
|
*/
|
|
static int flush_trash_fifo(struct most_channel *c)
|
|
{
|
|
struct mbo *mbo, *tmp;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
list_for_each_entry_safe(mbo, tmp, &c->trash_fifo, list) {
|
|
list_del(&mbo->list);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
most_free_mbo_coherent(mbo);
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t available_directions_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
unsigned int i = c->channel_id;
|
|
|
|
strcpy(buf, "");
|
|
if (c->iface->channel_vector[i].direction & MOST_CH_RX)
|
|
strcat(buf, "rx ");
|
|
if (c->iface->channel_vector[i].direction & MOST_CH_TX)
|
|
strcat(buf, "tx ");
|
|
strcat(buf, "\n");
|
|
return strlen(buf);
|
|
}
|
|
|
|
static ssize_t available_datatypes_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
unsigned int i = c->channel_id;
|
|
|
|
strcpy(buf, "");
|
|
if (c->iface->channel_vector[i].data_type & MOST_CH_CONTROL)
|
|
strcat(buf, "control ");
|
|
if (c->iface->channel_vector[i].data_type & MOST_CH_ASYNC)
|
|
strcat(buf, "async ");
|
|
if (c->iface->channel_vector[i].data_type & MOST_CH_SYNC)
|
|
strcat(buf, "sync ");
|
|
if (c->iface->channel_vector[i].data_type & MOST_CH_ISOC)
|
|
strcat(buf, "isoc ");
|
|
strcat(buf, "\n");
|
|
return strlen(buf);
|
|
}
|
|
|
|
static ssize_t number_of_packet_buffers_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
unsigned int i = c->channel_id;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
c->iface->channel_vector[i].num_buffers_packet);
|
|
}
|
|
|
|
static ssize_t number_of_stream_buffers_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
unsigned int i = c->channel_id;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
c->iface->channel_vector[i].num_buffers_streaming);
|
|
}
|
|
|
|
static ssize_t size_of_packet_buffer_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
unsigned int i = c->channel_id;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
c->iface->channel_vector[i].buffer_size_packet);
|
|
}
|
|
|
|
static ssize_t size_of_stream_buffer_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
unsigned int i = c->channel_id;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
c->iface->channel_vector[i].buffer_size_streaming);
|
|
}
|
|
|
|
static ssize_t channel_starving_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", c->is_starving);
|
|
}
|
|
|
|
static ssize_t set_number_of_buffers_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.num_buffers);
|
|
}
|
|
|
|
static ssize_t set_number_of_buffers_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
int ret = kstrtou16(buf, 0, &c->cfg.num_buffers);
|
|
|
|
if (ret)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_buffer_size_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.buffer_size);
|
|
}
|
|
|
|
static ssize_t set_buffer_size_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
int ret = kstrtou16(buf, 0, &c->cfg.buffer_size);
|
|
|
|
if (ret)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_direction_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
if (c->cfg.direction & MOST_CH_TX)
|
|
return snprintf(buf, PAGE_SIZE, "tx\n");
|
|
else if (c->cfg.direction & MOST_CH_RX)
|
|
return snprintf(buf, PAGE_SIZE, "rx\n");
|
|
return snprintf(buf, PAGE_SIZE, "unconfigured\n");
|
|
}
|
|
|
|
static ssize_t set_direction_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
if (!strcmp(buf, "dir_rx\n")) {
|
|
c->cfg.direction = MOST_CH_RX;
|
|
} else if (!strcmp(buf, "rx\n")) {
|
|
c->cfg.direction = MOST_CH_RX;
|
|
} else if (!strcmp(buf, "dir_tx\n")) {
|
|
c->cfg.direction = MOST_CH_TX;
|
|
} else if (!strcmp(buf, "tx\n")) {
|
|
c->cfg.direction = MOST_CH_TX;
|
|
} else {
|
|
pr_info("WARN: invalid attribute settings\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_datatype_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int i;
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ch_data_type); i++) {
|
|
if (c->cfg.data_type & ch_data_type[i].most_ch_data_type)
|
|
return snprintf(buf, PAGE_SIZE, ch_data_type[i].name);
|
|
}
|
|
return snprintf(buf, PAGE_SIZE, "unconfigured\n");
|
|
}
|
|
|
|
static ssize_t set_datatype_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
int i;
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ch_data_type); i++) {
|
|
if (!strcmp(buf, ch_data_type[i].name)) {
|
|
c->cfg.data_type = ch_data_type[i].most_ch_data_type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(ch_data_type)) {
|
|
pr_info("WARN: invalid attribute settings\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_subbuffer_size_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.subbuffer_size);
|
|
}
|
|
|
|
static ssize_t set_subbuffer_size_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
int ret = kstrtou16(buf, 0, &c->cfg.subbuffer_size);
|
|
|
|
if (ret)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_packets_per_xact_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.packets_per_xact);
|
|
}
|
|
|
|
static ssize_t set_packets_per_xact_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
int ret = kstrtou16(buf, 0, &c->cfg.packets_per_xact);
|
|
|
|
if (ret)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_dbr_size_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.dbr_size);
|
|
}
|
|
|
|
static ssize_t set_dbr_size_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct most_channel *c = to_channel(dev);
|
|
int ret = kstrtou16(buf, 0, &c->cfg.dbr_size);
|
|
|
|
if (ret)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
#define DEV_ATTR(_name) (&dev_attr_##_name.attr)
|
|
|
|
static DEVICE_ATTR_RO(available_directions);
|
|
static DEVICE_ATTR_RO(available_datatypes);
|
|
static DEVICE_ATTR_RO(number_of_packet_buffers);
|
|
static DEVICE_ATTR_RO(number_of_stream_buffers);
|
|
static DEVICE_ATTR_RO(size_of_stream_buffer);
|
|
static DEVICE_ATTR_RO(size_of_packet_buffer);
|
|
static DEVICE_ATTR_RO(channel_starving);
|
|
static DEVICE_ATTR_RW(set_buffer_size);
|
|
static DEVICE_ATTR_RW(set_number_of_buffers);
|
|
static DEVICE_ATTR_RW(set_direction);
|
|
static DEVICE_ATTR_RW(set_datatype);
|
|
static DEVICE_ATTR_RW(set_subbuffer_size);
|
|
static DEVICE_ATTR_RW(set_packets_per_xact);
|
|
static DEVICE_ATTR_RW(set_dbr_size);
|
|
|
|
static struct attribute *channel_attrs[] = {
|
|
DEV_ATTR(available_directions),
|
|
DEV_ATTR(available_datatypes),
|
|
DEV_ATTR(number_of_packet_buffers),
|
|
DEV_ATTR(number_of_stream_buffers),
|
|
DEV_ATTR(size_of_stream_buffer),
|
|
DEV_ATTR(size_of_packet_buffer),
|
|
DEV_ATTR(channel_starving),
|
|
DEV_ATTR(set_buffer_size),
|
|
DEV_ATTR(set_number_of_buffers),
|
|
DEV_ATTR(set_direction),
|
|
DEV_ATTR(set_datatype),
|
|
DEV_ATTR(set_subbuffer_size),
|
|
DEV_ATTR(set_packets_per_xact),
|
|
DEV_ATTR(set_dbr_size),
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group channel_attr_group = {
|
|
.attrs = channel_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *channel_attr_groups[] = {
|
|
&channel_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
static ssize_t description_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_interface *iface = to_most_interface(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", iface->description);
|
|
}
|
|
|
|
static ssize_t interface_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct most_interface *iface = to_most_interface(dev);
|
|
|
|
switch (iface->interface) {
|
|
case ITYPE_LOOPBACK:
|
|
return snprintf(buf, PAGE_SIZE, "loopback\n");
|
|
case ITYPE_I2C:
|
|
return snprintf(buf, PAGE_SIZE, "i2c\n");
|
|
case ITYPE_I2S:
|
|
return snprintf(buf, PAGE_SIZE, "i2s\n");
|
|
case ITYPE_TSI:
|
|
return snprintf(buf, PAGE_SIZE, "tsi\n");
|
|
case ITYPE_HBI:
|
|
return snprintf(buf, PAGE_SIZE, "hbi\n");
|
|
case ITYPE_MEDIALB_DIM:
|
|
return snprintf(buf, PAGE_SIZE, "mlb_dim\n");
|
|
case ITYPE_MEDIALB_DIM2:
|
|
return snprintf(buf, PAGE_SIZE, "mlb_dim2\n");
|
|
case ITYPE_USB:
|
|
return snprintf(buf, PAGE_SIZE, "usb\n");
|
|
case ITYPE_PCIE:
|
|
return snprintf(buf, PAGE_SIZE, "pcie\n");
|
|
}
|
|
return snprintf(buf, PAGE_SIZE, "unknown\n");
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(description);
|
|
static DEVICE_ATTR_RO(interface);
|
|
|
|
static struct attribute *interface_attrs[] = {
|
|
DEV_ATTR(description),
|
|
DEV_ATTR(interface),
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group interface_attr_group = {
|
|
.attrs = interface_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *interface_attr_groups[] = {
|
|
&interface_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
static struct core_component *match_component(char *name)
|
|
{
|
|
struct core_component *comp;
|
|
|
|
list_for_each_entry(comp, &mc.comp_list, list) {
|
|
if (!strcmp(comp->name, name))
|
|
return comp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct show_links_data {
|
|
int offs;
|
|
char *buf;
|
|
};
|
|
|
|
static int print_links(struct device *dev, void *data)
|
|
{
|
|
struct show_links_data *d = data;
|
|
int offs = d->offs;
|
|
char *buf = d->buf;
|
|
struct most_channel *c;
|
|
struct most_interface *iface = to_most_interface(dev);
|
|
|
|
list_for_each_entry(c, &iface->p->channel_list, list) {
|
|
if (c->pipe0.comp) {
|
|
offs += snprintf(buf + offs,
|
|
PAGE_SIZE - offs,
|
|
"%s:%s:%s\n",
|
|
c->pipe0.comp->name,
|
|
dev_name(&iface->dev),
|
|
dev_name(&c->dev));
|
|
}
|
|
if (c->pipe1.comp) {
|
|
offs += snprintf(buf + offs,
|
|
PAGE_SIZE - offs,
|
|
"%s:%s:%s\n",
|
|
c->pipe1.comp->name,
|
|
dev_name(&iface->dev),
|
|
dev_name(&c->dev));
|
|
}
|
|
}
|
|
d->offs = offs;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t links_show(struct device_driver *drv, char *buf)
|
|
{
|
|
struct show_links_data d = { .buf = buf };
|
|
|
|
bus_for_each_dev(&mc.bus, NULL, &d, print_links);
|
|
return d.offs;
|
|
}
|
|
|
|
static ssize_t components_show(struct device_driver *drv, char *buf)
|
|
{
|
|
struct core_component *comp;
|
|
int offs = 0;
|
|
|
|
list_for_each_entry(comp, &mc.comp_list, list) {
|
|
offs += snprintf(buf + offs, PAGE_SIZE - offs, "%s\n",
|
|
comp->name);
|
|
}
|
|
return offs;
|
|
}
|
|
|
|
/**
|
|
* split_string - parses buf and extracts ':' separated substrings.
|
|
*
|
|
* @buf: complete string from attribute 'add_channel'
|
|
* @a: storage for 1st substring (=interface name)
|
|
* @b: storage for 2nd substring (=channel name)
|
|
* @c: storage for 3rd substring (=component name)
|
|
* @d: storage optional 4th substring (=user defined name)
|
|
*
|
|
* Examples:
|
|
*
|
|
* Input: "mdev0:ch6:cdev:my_channel\n" or
|
|
* "mdev0:ch6:cdev:my_channel"
|
|
*
|
|
* Output: *a -> "mdev0", *b -> "ch6", *c -> "cdev" *d -> "my_channel"
|
|
*
|
|
* Input: "mdev1:ep81:cdev\n"
|
|
* Output: *a -> "mdev1", *b -> "ep81", *c -> "cdev" *d -> ""
|
|
*
|
|
* Input: "mdev1:ep81"
|
|
* Output: *a -> "mdev1", *b -> "ep81", *c -> "cdev" *d == NULL
|
|
*/
|
|
static int split_string(char *buf, char **a, char **b, char **c, char **d)
|
|
{
|
|
*a = strsep(&buf, ":");
|
|
if (!*a)
|
|
return -EIO;
|
|
|
|
*b = strsep(&buf, ":\n");
|
|
if (!*b)
|
|
return -EIO;
|
|
|
|
*c = strsep(&buf, ":\n");
|
|
if (!*c)
|
|
return -EIO;
|
|
|
|
if (d)
|
|
*d = strsep(&buf, ":\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int match_bus_dev(struct device *dev, void *data)
|
|
{
|
|
char *mdev_name = data;
|
|
|
|
return !strcmp(dev_name(dev), mdev_name);
|
|
}
|
|
|
|
/**
|
|
* get_channel - get pointer to channel
|
|
* @mdev: name of the device interface
|
|
* @mdev_ch: name of channel
|
|
*/
|
|
static struct most_channel *get_channel(char *mdev, char *mdev_ch)
|
|
{
|
|
struct device *dev = NULL;
|
|
struct most_interface *iface;
|
|
struct most_channel *c, *tmp;
|
|
|
|
dev = bus_find_device(&mc.bus, NULL, mdev, match_bus_dev);
|
|
if (!dev)
|
|
return NULL;
|
|
iface = to_most_interface(dev);
|
|
list_for_each_entry_safe(c, tmp, &iface->p->channel_list, list) {
|
|
if (!strcmp(dev_name(&c->dev), mdev_ch))
|
|
return c;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
inline int link_channel_to_component(struct most_channel *c,
|
|
struct core_component *comp,
|
|
char *comp_param)
|
|
{
|
|
int ret;
|
|
struct core_component **comp_ptr;
|
|
|
|
if (!c->pipe0.comp)
|
|
comp_ptr = &c->pipe0.comp;
|
|
else if (!c->pipe1.comp)
|
|
comp_ptr = &c->pipe1.comp;
|
|
else
|
|
return -ENOSPC;
|
|
|
|
*comp_ptr = comp;
|
|
ret = comp->probe_channel(c->iface, c->channel_id, &c->cfg, comp_param);
|
|
if (ret) {
|
|
*comp_ptr = NULL;
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* add_link_store - store function for add_link attribute
|
|
* @drv: device driver
|
|
* @buf: buffer
|
|
* @len: buffer length
|
|
*
|
|
* This parses the string given by buf and splits it into
|
|
* four substrings. Note: last substring is optional. In case a cdev
|
|
* component is loaded the optional 4th substring will make up the name of
|
|
* device node in the /dev directory. If omitted, the device node will
|
|
* inherit the channel's name within sysfs.
|
|
*
|
|
* Searches for (device, channel) pair and probes the component
|
|
*
|
|
* Example:
|
|
* (1) echo "mdev0:ch6:cdev:my_rxchannel" >add_link
|
|
* (2) echo "mdev1:ep81:cdev" >add_link
|
|
*
|
|
* (1) would create the device node /dev/my_rxchannel
|
|
* (2) would create the device node /dev/mdev1-ep81
|
|
*/
|
|
static ssize_t add_link_store(struct device_driver *drv,
|
|
const char *buf,
|
|
size_t len)
|
|
{
|
|
struct most_channel *c;
|
|
struct core_component *comp;
|
|
char buffer[STRING_SIZE];
|
|
char *mdev;
|
|
char *mdev_ch;
|
|
char *comp_name;
|
|
char *comp_param;
|
|
char devnod_buf[STRING_SIZE];
|
|
int ret;
|
|
size_t max_len = min_t(size_t, len + 1, STRING_SIZE);
|
|
|
|
strlcpy(buffer, buf, max_len);
|
|
ret = split_string(buffer, &mdev, &mdev_ch, &comp_name, &comp_param);
|
|
if (ret)
|
|
return ret;
|
|
comp = match_component(comp_name);
|
|
if (!comp)
|
|
return -ENODEV;
|
|
if (!comp_param || *comp_param == 0) {
|
|
snprintf(devnod_buf, sizeof(devnod_buf), "%s-%s", mdev,
|
|
mdev_ch);
|
|
comp_param = devnod_buf;
|
|
}
|
|
|
|
c = get_channel(mdev, mdev_ch);
|
|
if (!c)
|
|
return -ENODEV;
|
|
|
|
ret = link_channel_to_component(c, comp, comp_param);
|
|
if (ret)
|
|
return ret;
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* remove_link_store - store function for remove_link attribute
|
|
* @drv: device driver
|
|
* @buf: buffer
|
|
* @len: buffer length
|
|
*
|
|
* Example:
|
|
* echo "mdev0:ep81" >remove_link
|
|
*/
|
|
static ssize_t remove_link_store(struct device_driver *drv,
|
|
const char *buf,
|
|
size_t len)
|
|
{
|
|
struct most_channel *c;
|
|
struct core_component *comp;
|
|
char buffer[STRING_SIZE];
|
|
char *mdev;
|
|
char *mdev_ch;
|
|
char *comp_name;
|
|
int ret;
|
|
size_t max_len = min_t(size_t, len + 1, STRING_SIZE);
|
|
|
|
strlcpy(buffer, buf, max_len);
|
|
ret = split_string(buffer, &mdev, &mdev_ch, &comp_name, NULL);
|
|
if (ret)
|
|
return ret;
|
|
comp = match_component(comp_name);
|
|
if (!comp)
|
|
return -ENODEV;
|
|
c = get_channel(mdev, mdev_ch);
|
|
if (!c)
|
|
return -ENODEV;
|
|
|
|
if (comp->disconnect_channel(c->iface, c->channel_id))
|
|
return -EIO;
|
|
if (c->pipe0.comp == comp)
|
|
c->pipe0.comp = NULL;
|
|
if (c->pipe1.comp == comp)
|
|
c->pipe1.comp = NULL;
|
|
return len;
|
|
}
|
|
|
|
#define DRV_ATTR(_name) (&driver_attr_##_name.attr)
|
|
|
|
static DRIVER_ATTR_RO(links);
|
|
static DRIVER_ATTR_RO(components);
|
|
static DRIVER_ATTR_WO(add_link);
|
|
static DRIVER_ATTR_WO(remove_link);
|
|
|
|
static struct attribute *mc_attrs[] = {
|
|
DRV_ATTR(links),
|
|
DRV_ATTR(components),
|
|
DRV_ATTR(add_link),
|
|
DRV_ATTR(remove_link),
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group mc_attr_group = {
|
|
.attrs = mc_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *mc_attr_groups[] = {
|
|
&mc_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
static int most_match(struct device *dev, struct device_driver *drv)
|
|
{
|
|
if (!strcmp(dev_name(dev), "most"))
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static inline void trash_mbo(struct mbo *mbo)
|
|
{
|
|
unsigned long flags;
|
|
struct most_channel *c = mbo->context;
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
list_add(&mbo->list, &c->trash_fifo);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
}
|
|
|
|
static bool hdm_mbo_ready(struct most_channel *c)
|
|
{
|
|
bool empty;
|
|
|
|
if (c->enqueue_halt)
|
|
return false;
|
|
|
|
spin_lock_irq(&c->fifo_lock);
|
|
empty = list_empty(&c->halt_fifo);
|
|
spin_unlock_irq(&c->fifo_lock);
|
|
|
|
return !empty;
|
|
}
|
|
|
|
static void nq_hdm_mbo(struct mbo *mbo)
|
|
{
|
|
unsigned long flags;
|
|
struct most_channel *c = mbo->context;
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
list_add_tail(&mbo->list, &c->halt_fifo);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
wake_up_interruptible(&c->hdm_fifo_wq);
|
|
}
|
|
|
|
static int hdm_enqueue_thread(void *data)
|
|
{
|
|
struct most_channel *c = data;
|
|
struct mbo *mbo;
|
|
int ret;
|
|
typeof(c->iface->enqueue) enqueue = c->iface->enqueue;
|
|
|
|
while (likely(!kthread_should_stop())) {
|
|
wait_event_interruptible(c->hdm_fifo_wq,
|
|
hdm_mbo_ready(c) ||
|
|
kthread_should_stop());
|
|
|
|
mutex_lock(&c->nq_mutex);
|
|
spin_lock_irq(&c->fifo_lock);
|
|
if (unlikely(c->enqueue_halt || list_empty(&c->halt_fifo))) {
|
|
spin_unlock_irq(&c->fifo_lock);
|
|
mutex_unlock(&c->nq_mutex);
|
|
continue;
|
|
}
|
|
|
|
mbo = list_pop_mbo(&c->halt_fifo);
|
|
spin_unlock_irq(&c->fifo_lock);
|
|
|
|
if (c->cfg.direction == MOST_CH_RX)
|
|
mbo->buffer_length = c->cfg.buffer_size;
|
|
|
|
ret = enqueue(mbo->ifp, mbo->hdm_channel_id, mbo);
|
|
mutex_unlock(&c->nq_mutex);
|
|
|
|
if (unlikely(ret)) {
|
|
pr_err("hdm enqueue failed\n");
|
|
nq_hdm_mbo(mbo);
|
|
c->hdm_enqueue_task = NULL;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int run_enqueue_thread(struct most_channel *c, int channel_id)
|
|
{
|
|
struct task_struct *task =
|
|
kthread_run(hdm_enqueue_thread, c, "hdm_fifo_%d",
|
|
channel_id);
|
|
|
|
if (IS_ERR(task))
|
|
return PTR_ERR(task);
|
|
|
|
c->hdm_enqueue_task = task;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* arm_mbo - recycle MBO for further usage
|
|
* @mbo: most buffer
|
|
*
|
|
* This puts an MBO back to the list to have it ready for up coming
|
|
* tx transactions.
|
|
*
|
|
* In case the MBO belongs to a channel that recently has been
|
|
* poisoned, the MBO is scheduled to be trashed.
|
|
* Calls the completion handler of an attached component.
|
|
*/
|
|
static void arm_mbo(struct mbo *mbo)
|
|
{
|
|
unsigned long flags;
|
|
struct most_channel *c;
|
|
|
|
c = mbo->context;
|
|
|
|
if (c->is_poisoned) {
|
|
trash_mbo(mbo);
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
++*mbo->num_buffers_ptr;
|
|
list_add_tail(&mbo->list, &c->fifo);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
|
|
if (c->pipe0.refs && c->pipe0.comp->tx_completion)
|
|
c->pipe0.comp->tx_completion(c->iface, c->channel_id);
|
|
|
|
if (c->pipe1.refs && c->pipe1.comp->tx_completion)
|
|
c->pipe1.comp->tx_completion(c->iface, c->channel_id);
|
|
}
|
|
|
|
/**
|
|
* arm_mbo_chain - helper function that arms an MBO chain for the HDM
|
|
* @c: pointer to interface channel
|
|
* @dir: direction of the channel
|
|
* @compl: pointer to completion function
|
|
*
|
|
* This allocates buffer objects including the containing DMA coherent
|
|
* buffer and puts them in the fifo.
|
|
* Buffers of Rx channels are put in the kthread fifo, hence immediately
|
|
* submitted to the HDM.
|
|
*
|
|
* Returns the number of allocated and enqueued MBOs.
|
|
*/
|
|
static int arm_mbo_chain(struct most_channel *c, int dir,
|
|
void (*compl)(struct mbo *))
|
|
{
|
|
unsigned int i;
|
|
struct mbo *mbo;
|
|
unsigned long flags;
|
|
u32 coherent_buf_size = c->cfg.buffer_size + c->cfg.extra_len;
|
|
|
|
atomic_set(&c->mbo_nq_level, 0);
|
|
|
|
for (i = 0; i < c->cfg.num_buffers; i++) {
|
|
mbo = kzalloc(sizeof(*mbo), GFP_KERNEL);
|
|
if (!mbo)
|
|
goto flush_fifos;
|
|
|
|
mbo->context = c;
|
|
mbo->ifp = c->iface;
|
|
mbo->hdm_channel_id = c->channel_id;
|
|
if (c->iface->dma_alloc) {
|
|
mbo->virt_address =
|
|
c->iface->dma_alloc(mbo, coherent_buf_size);
|
|
} else {
|
|
mbo->virt_address =
|
|
kzalloc(coherent_buf_size, GFP_KERNEL);
|
|
}
|
|
if (!mbo->virt_address)
|
|
goto release_mbo;
|
|
|
|
mbo->complete = compl;
|
|
mbo->num_buffers_ptr = &dummy_num_buffers;
|
|
if (dir == MOST_CH_RX) {
|
|
nq_hdm_mbo(mbo);
|
|
atomic_inc(&c->mbo_nq_level);
|
|
} else {
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
list_add_tail(&mbo->list, &c->fifo);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
}
|
|
}
|
|
return c->cfg.num_buffers;
|
|
|
|
release_mbo:
|
|
kfree(mbo);
|
|
|
|
flush_fifos:
|
|
flush_channel_fifos(c);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* most_submit_mbo - submits an MBO to fifo
|
|
* @mbo: most buffer
|
|
*/
|
|
void most_submit_mbo(struct mbo *mbo)
|
|
{
|
|
if (WARN_ONCE(!mbo || !mbo->context,
|
|
"bad mbo or missing channel reference\n"))
|
|
return;
|
|
|
|
nq_hdm_mbo(mbo);
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_submit_mbo);
|
|
|
|
/**
|
|
* most_write_completion - write completion handler
|
|
* @mbo: most buffer
|
|
*
|
|
* This recycles the MBO for further usage. In case the channel has been
|
|
* poisoned, the MBO is scheduled to be trashed.
|
|
*/
|
|
static void most_write_completion(struct mbo *mbo)
|
|
{
|
|
struct most_channel *c;
|
|
|
|
c = mbo->context;
|
|
if (mbo->status == MBO_E_INVAL)
|
|
pr_info("WARN: Tx MBO status: invalid\n");
|
|
if (unlikely(c->is_poisoned || (mbo->status == MBO_E_CLOSE)))
|
|
trash_mbo(mbo);
|
|
else
|
|
arm_mbo(mbo);
|
|
}
|
|
|
|
int channel_has_mbo(struct most_interface *iface, int id,
|
|
struct core_component *comp)
|
|
{
|
|
struct most_channel *c = iface->p->channel[id];
|
|
unsigned long flags;
|
|
int empty;
|
|
|
|
if (unlikely(!c))
|
|
return -EINVAL;
|
|
|
|
if (c->pipe0.refs && c->pipe1.refs &&
|
|
((comp == c->pipe0.comp && c->pipe0.num_buffers <= 0) ||
|
|
(comp == c->pipe1.comp && c->pipe1.num_buffers <= 0)))
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
empty = list_empty(&c->fifo);
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
return !empty;
|
|
}
|
|
EXPORT_SYMBOL_GPL(channel_has_mbo);
|
|
|
|
/**
|
|
* most_get_mbo - get pointer to an MBO of pool
|
|
* @iface: pointer to interface instance
|
|
* @id: channel ID
|
|
* @comp: driver component
|
|
*
|
|
* This attempts to get a free buffer out of the channel fifo.
|
|
* Returns a pointer to MBO on success or NULL otherwise.
|
|
*/
|
|
struct mbo *most_get_mbo(struct most_interface *iface, int id,
|
|
struct core_component *comp)
|
|
{
|
|
struct mbo *mbo;
|
|
struct most_channel *c;
|
|
unsigned long flags;
|
|
int *num_buffers_ptr;
|
|
|
|
c = iface->p->channel[id];
|
|
if (unlikely(!c))
|
|
return NULL;
|
|
|
|
if (c->pipe0.refs && c->pipe1.refs &&
|
|
((comp == c->pipe0.comp && c->pipe0.num_buffers <= 0) ||
|
|
(comp == c->pipe1.comp && c->pipe1.num_buffers <= 0)))
|
|
return NULL;
|
|
|
|
if (comp == c->pipe0.comp)
|
|
num_buffers_ptr = &c->pipe0.num_buffers;
|
|
else if (comp == c->pipe1.comp)
|
|
num_buffers_ptr = &c->pipe1.num_buffers;
|
|
else
|
|
num_buffers_ptr = &dummy_num_buffers;
|
|
|
|
spin_lock_irqsave(&c->fifo_lock, flags);
|
|
if (list_empty(&c->fifo)) {
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
return NULL;
|
|
}
|
|
mbo = list_pop_mbo(&c->fifo);
|
|
--*num_buffers_ptr;
|
|
spin_unlock_irqrestore(&c->fifo_lock, flags);
|
|
|
|
mbo->num_buffers_ptr = num_buffers_ptr;
|
|
mbo->buffer_length = c->cfg.buffer_size;
|
|
return mbo;
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_get_mbo);
|
|
|
|
/**
|
|
* most_put_mbo - return buffer to pool
|
|
* @mbo: most buffer
|
|
*/
|
|
void most_put_mbo(struct mbo *mbo)
|
|
{
|
|
struct most_channel *c = mbo->context;
|
|
|
|
if (c->cfg.direction == MOST_CH_TX) {
|
|
arm_mbo(mbo);
|
|
return;
|
|
}
|
|
nq_hdm_mbo(mbo);
|
|
atomic_inc(&c->mbo_nq_level);
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_put_mbo);
|
|
|
|
/**
|
|
* most_read_completion - read completion handler
|
|
* @mbo: most buffer
|
|
*
|
|
* This function is called by the HDM when data has been received from the
|
|
* hardware and copied to the buffer of the MBO.
|
|
*
|
|
* In case the channel has been poisoned it puts the buffer in the trash queue.
|
|
* Otherwise, it passes the buffer to an component for further processing.
|
|
*/
|
|
static void most_read_completion(struct mbo *mbo)
|
|
{
|
|
struct most_channel *c = mbo->context;
|
|
|
|
if (unlikely(c->is_poisoned || (mbo->status == MBO_E_CLOSE))) {
|
|
trash_mbo(mbo);
|
|
return;
|
|
}
|
|
|
|
if (mbo->status == MBO_E_INVAL) {
|
|
nq_hdm_mbo(mbo);
|
|
atomic_inc(&c->mbo_nq_level);
|
|
return;
|
|
}
|
|
|
|
if (atomic_sub_and_test(1, &c->mbo_nq_level))
|
|
c->is_starving = 1;
|
|
|
|
if (c->pipe0.refs && c->pipe0.comp->rx_completion &&
|
|
c->pipe0.comp->rx_completion(mbo) == 0)
|
|
return;
|
|
|
|
if (c->pipe1.refs && c->pipe1.comp->rx_completion &&
|
|
c->pipe1.comp->rx_completion(mbo) == 0)
|
|
return;
|
|
|
|
most_put_mbo(mbo);
|
|
}
|
|
|
|
/**
|
|
* most_start_channel - prepares a channel for communication
|
|
* @iface: pointer to interface instance
|
|
* @id: channel ID
|
|
* @comp: driver component
|
|
*
|
|
* This prepares the channel for usage. Cross-checks whether the
|
|
* channel's been properly configured.
|
|
*
|
|
* Returns 0 on success or error code otherwise.
|
|
*/
|
|
int most_start_channel(struct most_interface *iface, int id,
|
|
struct core_component *comp)
|
|
{
|
|
int num_buffer;
|
|
int ret;
|
|
struct most_channel *c = iface->p->channel[id];
|
|
|
|
if (unlikely(!c))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&c->start_mutex);
|
|
if (c->pipe0.refs + c->pipe1.refs > 0)
|
|
goto out; /* already started by another component */
|
|
|
|
if (!try_module_get(iface->mod)) {
|
|
pr_info("failed to acquire HDM lock\n");
|
|
mutex_unlock(&c->start_mutex);
|
|
return -ENOLCK;
|
|
}
|
|
|
|
c->cfg.extra_len = 0;
|
|
if (c->iface->configure(c->iface, c->channel_id, &c->cfg)) {
|
|
pr_info("channel configuration failed. Go check settings...\n");
|
|
ret = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
init_waitqueue_head(&c->hdm_fifo_wq);
|
|
|
|
if (c->cfg.direction == MOST_CH_RX)
|
|
num_buffer = arm_mbo_chain(c, c->cfg.direction,
|
|
most_read_completion);
|
|
else
|
|
num_buffer = arm_mbo_chain(c, c->cfg.direction,
|
|
most_write_completion);
|
|
if (unlikely(!num_buffer)) {
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
ret = run_enqueue_thread(c, id);
|
|
if (ret)
|
|
goto error;
|
|
|
|
c->is_starving = 0;
|
|
c->pipe0.num_buffers = c->cfg.num_buffers / 2;
|
|
c->pipe1.num_buffers = c->cfg.num_buffers - c->pipe0.num_buffers;
|
|
atomic_set(&c->mbo_ref, num_buffer);
|
|
|
|
out:
|
|
if (comp == c->pipe0.comp)
|
|
c->pipe0.refs++;
|
|
if (comp == c->pipe1.comp)
|
|
c->pipe1.refs++;
|
|
mutex_unlock(&c->start_mutex);
|
|
return 0;
|
|
|
|
error:
|
|
module_put(iface->mod);
|
|
mutex_unlock(&c->start_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_start_channel);
|
|
|
|
/**
|
|
* most_stop_channel - stops a running channel
|
|
* @iface: pointer to interface instance
|
|
* @id: channel ID
|
|
* @comp: driver component
|
|
*/
|
|
int most_stop_channel(struct most_interface *iface, int id,
|
|
struct core_component *comp)
|
|
{
|
|
struct most_channel *c;
|
|
|
|
if (unlikely((!iface) || (id >= iface->num_channels) || (id < 0))) {
|
|
pr_err("Bad interface or index out of range\n");
|
|
return -EINVAL;
|
|
}
|
|
c = iface->p->channel[id];
|
|
if (unlikely(!c))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&c->start_mutex);
|
|
if (c->pipe0.refs + c->pipe1.refs >= 2)
|
|
goto out;
|
|
|
|
if (c->hdm_enqueue_task)
|
|
kthread_stop(c->hdm_enqueue_task);
|
|
c->hdm_enqueue_task = NULL;
|
|
|
|
if (iface->mod)
|
|
module_put(iface->mod);
|
|
|
|
c->is_poisoned = true;
|
|
if (c->iface->poison_channel(c->iface, c->channel_id)) {
|
|
pr_err("Cannot stop channel %d of mdev %s\n", c->channel_id,
|
|
c->iface->description);
|
|
mutex_unlock(&c->start_mutex);
|
|
return -EAGAIN;
|
|
}
|
|
flush_trash_fifo(c);
|
|
flush_channel_fifos(c);
|
|
|
|
#ifdef CMPL_INTERRUPTIBLE
|
|
if (wait_for_completion_interruptible(&c->cleanup)) {
|
|
pr_info("Interrupted while clean up ch %d\n", c->channel_id);
|
|
mutex_unlock(&c->start_mutex);
|
|
return -EINTR;
|
|
}
|
|
#else
|
|
wait_for_completion(&c->cleanup);
|
|
#endif
|
|
c->is_poisoned = false;
|
|
|
|
out:
|
|
if (comp == c->pipe0.comp)
|
|
c->pipe0.refs--;
|
|
if (comp == c->pipe1.comp)
|
|
c->pipe1.refs--;
|
|
mutex_unlock(&c->start_mutex);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_stop_channel);
|
|
|
|
/**
|
|
* most_register_component - registers a driver component with the core
|
|
* @comp: driver component
|
|
*/
|
|
int most_register_component(struct core_component *comp)
|
|
{
|
|
if (!comp) {
|
|
pr_err("Bad component\n");
|
|
return -EINVAL;
|
|
}
|
|
list_add_tail(&comp->list, &mc.comp_list);
|
|
pr_info("registered new core component %s\n", comp->name);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_register_component);
|
|
|
|
static int disconnect_channels(struct device *dev, void *data)
|
|
{
|
|
struct most_interface *iface;
|
|
struct most_channel *c, *tmp;
|
|
struct core_component *comp = data;
|
|
|
|
iface = to_most_interface(dev);
|
|
list_for_each_entry_safe(c, tmp, &iface->p->channel_list, list) {
|
|
if (c->pipe0.comp == comp || c->pipe1.comp == comp)
|
|
comp->disconnect_channel(c->iface, c->channel_id);
|
|
if (c->pipe0.comp == comp)
|
|
c->pipe0.comp = NULL;
|
|
if (c->pipe1.comp == comp)
|
|
c->pipe1.comp = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* most_deregister_component - deregisters a driver component with the core
|
|
* @comp: driver component
|
|
*/
|
|
int most_deregister_component(struct core_component *comp)
|
|
{
|
|
if (!comp) {
|
|
pr_err("Bad component\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
bus_for_each_dev(&mc.bus, NULL, comp, disconnect_channels);
|
|
list_del(&comp->list);
|
|
pr_info("deregistering component %s\n", comp->name);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_deregister_component);
|
|
|
|
static void release_interface(struct device *dev)
|
|
{
|
|
pr_info("releasing interface dev %s...\n", dev_name(dev));
|
|
}
|
|
|
|
static void release_channel(struct device *dev)
|
|
{
|
|
pr_info("releasing channel dev %s...\n", dev_name(dev));
|
|
}
|
|
|
|
/**
|
|
* most_register_interface - registers an interface with core
|
|
* @iface: device interface
|
|
*
|
|
* Allocates and initializes a new interface instance and all of its channels.
|
|
* Returns a pointer to kobject or an error pointer.
|
|
*/
|
|
int most_register_interface(struct most_interface *iface)
|
|
{
|
|
unsigned int i;
|
|
int id;
|
|
struct most_channel *c;
|
|
|
|
if (!iface || !iface->enqueue || !iface->configure ||
|
|
!iface->poison_channel || (iface->num_channels > MAX_CHANNELS)) {
|
|
pr_err("Bad interface or channel overflow\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
id = ida_simple_get(&mdev_id, 0, 0, GFP_KERNEL);
|
|
if (id < 0) {
|
|
pr_info("Failed to alloc mdev ID\n");
|
|
return id;
|
|
}
|
|
|
|
iface->p = kzalloc(sizeof(*iface->p), GFP_KERNEL);
|
|
if (!iface->p) {
|
|
ida_simple_remove(&mdev_id, id);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&iface->p->channel_list);
|
|
iface->p->dev_id = id;
|
|
snprintf(iface->p->name, STRING_SIZE, "mdev%d", id);
|
|
iface->dev.init_name = iface->p->name;
|
|
iface->dev.bus = &mc.bus;
|
|
iface->dev.parent = &mc.dev;
|
|
iface->dev.groups = interface_attr_groups;
|
|
iface->dev.release = release_interface;
|
|
if (device_register(&iface->dev)) {
|
|
pr_err("registering iface->dev failed\n");
|
|
kfree(iface->p);
|
|
ida_simple_remove(&mdev_id, id);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < iface->num_channels; i++) {
|
|
const char *name_suffix = iface->channel_vector[i].name_suffix;
|
|
|
|
c = kzalloc(sizeof(*c), GFP_KERNEL);
|
|
if (!c)
|
|
goto free_instance;
|
|
if (!name_suffix)
|
|
snprintf(c->name, STRING_SIZE, "ch%d", i);
|
|
else
|
|
snprintf(c->name, STRING_SIZE, "%s", name_suffix);
|
|
c->dev.init_name = c->name;
|
|
c->dev.parent = &iface->dev;
|
|
c->dev.groups = channel_attr_groups;
|
|
c->dev.release = release_channel;
|
|
if (device_register(&c->dev)) {
|
|
pr_err("registering c->dev failed\n");
|
|
goto free_instance_nodev;
|
|
}
|
|
iface->p->channel[i] = c;
|
|
c->is_starving = 0;
|
|
c->iface = iface;
|
|
c->channel_id = i;
|
|
c->keep_mbo = false;
|
|
c->enqueue_halt = false;
|
|
c->is_poisoned = false;
|
|
c->cfg.direction = 0;
|
|
c->cfg.data_type = 0;
|
|
c->cfg.num_buffers = 0;
|
|
c->cfg.buffer_size = 0;
|
|
c->cfg.subbuffer_size = 0;
|
|
c->cfg.packets_per_xact = 0;
|
|
spin_lock_init(&c->fifo_lock);
|
|
INIT_LIST_HEAD(&c->fifo);
|
|
INIT_LIST_HEAD(&c->trash_fifo);
|
|
INIT_LIST_HEAD(&c->halt_fifo);
|
|
init_completion(&c->cleanup);
|
|
atomic_set(&c->mbo_ref, 0);
|
|
mutex_init(&c->start_mutex);
|
|
mutex_init(&c->nq_mutex);
|
|
list_add_tail(&c->list, &iface->p->channel_list);
|
|
}
|
|
pr_info("registered new device mdev%d (%s)\n",
|
|
id, iface->description);
|
|
return 0;
|
|
|
|
free_instance_nodev:
|
|
kfree(c);
|
|
|
|
free_instance:
|
|
while (i > 0) {
|
|
c = iface->p->channel[--i];
|
|
device_unregister(&c->dev);
|
|
kfree(c);
|
|
}
|
|
kfree(iface->p);
|
|
device_unregister(&iface->dev);
|
|
ida_simple_remove(&mdev_id, id);
|
|
return -ENOMEM;
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_register_interface);
|
|
|
|
/**
|
|
* most_deregister_interface - deregisters an interface with core
|
|
* @iface: device interface
|
|
*
|
|
* Before removing an interface instance from the list, all running
|
|
* channels are stopped and poisoned.
|
|
*/
|
|
void most_deregister_interface(struct most_interface *iface)
|
|
{
|
|
int i;
|
|
struct most_channel *c;
|
|
|
|
pr_info("deregistering device %s (%s)\n", dev_name(&iface->dev),
|
|
iface->description);
|
|
for (i = 0; i < iface->num_channels; i++) {
|
|
c = iface->p->channel[i];
|
|
if (c->pipe0.comp)
|
|
c->pipe0.comp->disconnect_channel(c->iface,
|
|
c->channel_id);
|
|
if (c->pipe1.comp)
|
|
c->pipe1.comp->disconnect_channel(c->iface,
|
|
c->channel_id);
|
|
c->pipe0.comp = NULL;
|
|
c->pipe1.comp = NULL;
|
|
list_del(&c->list);
|
|
device_unregister(&c->dev);
|
|
kfree(c);
|
|
}
|
|
|
|
ida_simple_remove(&mdev_id, iface->p->dev_id);
|
|
kfree(iface->p);
|
|
device_unregister(&iface->dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_deregister_interface);
|
|
|
|
/**
|
|
* most_stop_enqueue - prevents core from enqueueing MBOs
|
|
* @iface: pointer to interface
|
|
* @id: channel id
|
|
*
|
|
* This is called by an HDM that _cannot_ attend to its duties and
|
|
* is imminent to get run over by the core. The core is not going to
|
|
* enqueue any further packets unless the flagging HDM calls
|
|
* most_resume enqueue().
|
|
*/
|
|
void most_stop_enqueue(struct most_interface *iface, int id)
|
|
{
|
|
struct most_channel *c = iface->p->channel[id];
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
mutex_lock(&c->nq_mutex);
|
|
c->enqueue_halt = true;
|
|
mutex_unlock(&c->nq_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_stop_enqueue);
|
|
|
|
/**
|
|
* most_resume_enqueue - allow core to enqueue MBOs again
|
|
* @iface: pointer to interface
|
|
* @id: channel id
|
|
*
|
|
* This clears the enqueue halt flag and enqueues all MBOs currently
|
|
* sitting in the wait fifo.
|
|
*/
|
|
void most_resume_enqueue(struct most_interface *iface, int id)
|
|
{
|
|
struct most_channel *c = iface->p->channel[id];
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
mutex_lock(&c->nq_mutex);
|
|
c->enqueue_halt = false;
|
|
mutex_unlock(&c->nq_mutex);
|
|
|
|
wake_up_interruptible(&c->hdm_fifo_wq);
|
|
}
|
|
EXPORT_SYMBOL_GPL(most_resume_enqueue);
|
|
|
|
static void release_most_sub(struct device *dev)
|
|
{
|
|
pr_info("releasing most_subsystem\n");
|
|
}
|
|
|
|
static int __init most_init(void)
|
|
{
|
|
int err;
|
|
|
|
pr_info("init()\n");
|
|
INIT_LIST_HEAD(&mc.comp_list);
|
|
ida_init(&mdev_id);
|
|
|
|
mc.bus.name = "most",
|
|
mc.bus.match = most_match,
|
|
mc.drv.name = "most_core",
|
|
mc.drv.bus = &mc.bus,
|
|
mc.drv.groups = mc_attr_groups;
|
|
|
|
err = bus_register(&mc.bus);
|
|
if (err) {
|
|
pr_info("Cannot register most bus\n");
|
|
return err;
|
|
}
|
|
err = driver_register(&mc.drv);
|
|
if (err) {
|
|
pr_info("Cannot register core driver\n");
|
|
goto exit_bus;
|
|
}
|
|
mc.dev.init_name = "most_bus";
|
|
mc.dev.release = release_most_sub;
|
|
if (device_register(&mc.dev)) {
|
|
err = -ENOMEM;
|
|
goto exit_driver;
|
|
}
|
|
|
|
return 0;
|
|
|
|
exit_driver:
|
|
driver_unregister(&mc.drv);
|
|
exit_bus:
|
|
bus_unregister(&mc.bus);
|
|
return err;
|
|
}
|
|
|
|
static void __exit most_exit(void)
|
|
{
|
|
pr_info("exit core module\n");
|
|
device_unregister(&mc.dev);
|
|
driver_unregister(&mc.drv);
|
|
bus_unregister(&mc.bus);
|
|
ida_destroy(&mdev_id);
|
|
}
|
|
|
|
module_init(most_init);
|
|
module_exit(most_exit);
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>");
|
|
MODULE_DESCRIPTION("Core module of stacked MOST Linux driver");
|