/* * core.c - Implementation of core module of MOST Linux driver stack * * Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This file is licensed under GPLv2. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mostcore.h" #define MAX_CHANNELS 64 #define STRING_SIZE 80 static struct class *most_class; static struct device *class_glue_dir; static struct ida mdev_id; static int dummy_num_buffers; struct most_c_aim_obj { struct most_aim *ptr; int refs; int num_buffers; }; struct most_c_obj { struct kobject kobj; struct completion cleanup; atomic_t mbo_ref; atomic_t mbo_nq_level; u16 channel_id; bool is_poisoned; struct mutex start_mutex; int is_starving; struct most_interface *iface; struct most_inst_obj *inst; 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 most_c_aim_obj aim0; struct most_c_aim_obj aim1; struct list_head trash_fifo; struct task_struct *hdm_enqueue_task; wait_queue_head_t hdm_fifo_wq; }; #define to_c_obj(d) container_of(d, struct most_c_obj, kobj) struct most_inst_obj { int dev_id; struct most_interface *iface; struct list_head channel_list; struct most_c_obj *channel[MAX_CHANNELS]; struct kobject kobj; struct list_head list; }; #define to_inst_obj(d) container_of(d, struct most_inst_obj, kobj) /** * 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; \ }) /* ___ ___ * ___C H A N N E L___ */ /** * struct most_c_attr - to access the attributes of a channel object * @attr: attributes of a channel * @show: pointer to the show function * @store: pointer to the store function */ struct most_c_attr { struct attribute attr; ssize_t (*show)(struct most_c_obj *d, struct most_c_attr *attr, char *buf); ssize_t (*store)(struct most_c_obj *d, struct most_c_attr *attr, const char *buf, size_t count); }; #define to_channel_attr(a) container_of(a, struct most_c_attr, attr) #define MOST_CHNL_ATTR(_name, _mode, _show, _store) \ struct most_c_attr most_chnl_attr_##_name = \ __ATTR(_name, _mode, _show, _store) /** * channel_attr_show - show function of channel object * @kobj: pointer to its kobject * @attr: pointer to its attributes * @buf: buffer */ static ssize_t channel_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct most_c_attr *channel_attr = to_channel_attr(attr); struct most_c_obj *c_obj = to_c_obj(kobj); if (!channel_attr->show) return -EIO; return channel_attr->show(c_obj, channel_attr, buf); } /** * channel_attr_store - store function of channel object * @kobj: pointer to its kobject * @attr: pointer to its attributes * @buf: buffer * @len: length of buffer */ static ssize_t channel_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct most_c_attr *channel_attr = to_channel_attr(attr); struct most_c_obj *c_obj = to_c_obj(kobj); if (!channel_attr->store) return -EIO; return channel_attr->store(c_obj, channel_attr, buf, len); } static const struct sysfs_ops most_channel_sysfs_ops = { .show = channel_attr_show, .store = channel_attr_store, }; /** * most_free_mbo_coherent - free an MBO and its coherent buffer * @mbo: buffer to be released * */ static void most_free_mbo_coherent(struct mbo *mbo) { struct most_c_obj *c = mbo->context; u16 const coherent_buf_size = c->cfg.buffer_size + c->cfg.extra_len; dma_free_coherent(NULL, coherent_buf_size, mbo->virt_address, mbo->bus_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_c_obj *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_c_obj *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; } /** * most_channel_release - release function of channel object * @kobj: pointer to channel's kobject */ static void most_channel_release(struct kobject *kobj) { struct most_c_obj *c = to_c_obj(kobj); kfree(c); } static ssize_t show_available_directions(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { unsigned int i = c->channel_id; strcpy(buf, ""); if (c->iface->channel_vector[i].direction & MOST_CH_RX) strcat(buf, "dir_rx "); if (c->iface->channel_vector[i].direction & MOST_CH_TX) strcat(buf, "dir_tx "); strcat(buf, "\n"); return strlen(buf) + 1; } static ssize_t show_available_datatypes(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { 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_AVP) strcat(buf, "isoc_avp "); strcat(buf, "\n"); return strlen(buf) + 1; } static ssize_t show_number_of_packet_buffers(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { unsigned int i = c->channel_id; return snprintf(buf, PAGE_SIZE, "%d\n", c->iface->channel_vector[i].num_buffers_packet); } static ssize_t show_number_of_stream_buffers(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { unsigned int i = c->channel_id; return snprintf(buf, PAGE_SIZE, "%d\n", c->iface->channel_vector[i].num_buffers_streaming); } static ssize_t show_size_of_packet_buffer(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { unsigned int i = c->channel_id; return snprintf(buf, PAGE_SIZE, "%d\n", c->iface->channel_vector[i].buffer_size_packet); } static ssize_t show_size_of_stream_buffer(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { unsigned int i = c->channel_id; return snprintf(buf, PAGE_SIZE, "%d\n", c->iface->channel_vector[i].buffer_size_streaming); } static ssize_t show_channel_starving(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%d\n", c->is_starving); } #define create_show_channel_attribute(val) \ static MOST_CHNL_ATTR(val, S_IRUGO, show_##val, NULL) create_show_channel_attribute(available_directions); create_show_channel_attribute(available_datatypes); create_show_channel_attribute(number_of_packet_buffers); create_show_channel_attribute(number_of_stream_buffers); create_show_channel_attribute(size_of_stream_buffer); create_show_channel_attribute(size_of_packet_buffer); create_show_channel_attribute(channel_starving); static ssize_t show_set_number_of_buffers(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.num_buffers); } static ssize_t store_set_number_of_buffers(struct most_c_obj *c, struct most_c_attr *attr, const char *buf, size_t count) { int ret = kstrtou16(buf, 0, &c->cfg.num_buffers); if (ret) return ret; return count; } static ssize_t show_set_buffer_size(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.buffer_size); } static ssize_t store_set_buffer_size(struct most_c_obj *c, struct most_c_attr *attr, const char *buf, size_t count) { int ret = kstrtou16(buf, 0, &c->cfg.buffer_size); if (ret) return ret; return count; } static ssize_t show_set_direction(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { if (c->cfg.direction & MOST_CH_TX) return snprintf(buf, PAGE_SIZE, "dir_tx\n"); else if (c->cfg.direction & MOST_CH_RX) return snprintf(buf, PAGE_SIZE, "dir_rx\n"); return snprintf(buf, PAGE_SIZE, "unconfigured\n"); } static ssize_t store_set_direction(struct most_c_obj *c, struct most_c_attr *attr, const char *buf, size_t count) { if (!strcmp(buf, "dir_rx\n")) { c->cfg.direction = MOST_CH_RX; } else if (!strcmp(buf, "dir_tx\n")) { c->cfg.direction = MOST_CH_TX; } else { pr_info("WARN: invalid attribute settings\n"); return -EINVAL; } return count; } static ssize_t show_set_datatype(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { if (c->cfg.data_type & MOST_CH_CONTROL) return snprintf(buf, PAGE_SIZE, "control\n"); else if (c->cfg.data_type & MOST_CH_ASYNC) return snprintf(buf, PAGE_SIZE, "async\n"); else if (c->cfg.data_type & MOST_CH_SYNC) return snprintf(buf, PAGE_SIZE, "sync\n"); else if (c->cfg.data_type & MOST_CH_ISOC_AVP) return snprintf(buf, PAGE_SIZE, "isoc_avp\n"); return snprintf(buf, PAGE_SIZE, "unconfigured\n"); } static ssize_t store_set_datatype(struct most_c_obj *c, struct most_c_attr *attr, const char *buf, size_t count) { if (!strcmp(buf, "control\n")) { c->cfg.data_type = MOST_CH_CONTROL; } else if (!strcmp(buf, "async\n")) { c->cfg.data_type = MOST_CH_ASYNC; } else if (!strcmp(buf, "sync\n")) { c->cfg.data_type = MOST_CH_SYNC; } else if (!strcmp(buf, "isoc_avp\n")) { c->cfg.data_type = MOST_CH_ISOC_AVP; } else { pr_info("WARN: invalid attribute settings\n"); return -EINVAL; } return count; } static ssize_t show_set_subbuffer_size(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.subbuffer_size); } static ssize_t store_set_subbuffer_size(struct most_c_obj *c, struct most_c_attr *attr, const char *buf, size_t count) { int ret = kstrtou16(buf, 0, &c->cfg.subbuffer_size); if (ret) return ret; return count; } static ssize_t show_set_packets_per_xact(struct most_c_obj *c, struct most_c_attr *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.packets_per_xact); } static ssize_t store_set_packets_per_xact(struct most_c_obj *c, struct most_c_attr *attr, const char *buf, size_t count) { int ret = kstrtou16(buf, 0, &c->cfg.packets_per_xact); if (ret) return ret; return count; } #define create_channel_attribute(value) \ static MOST_CHNL_ATTR(value, S_IRUGO | S_IWUSR, \ show_##value, \ store_##value) create_channel_attribute(set_buffer_size); create_channel_attribute(set_number_of_buffers); create_channel_attribute(set_direction); create_channel_attribute(set_datatype); create_channel_attribute(set_subbuffer_size); create_channel_attribute(set_packets_per_xact); /** * most_channel_def_attrs - array of default attributes of channel object */ static struct attribute *most_channel_def_attrs[] = { &most_chnl_attr_available_directions.attr, &most_chnl_attr_available_datatypes.attr, &most_chnl_attr_number_of_packet_buffers.attr, &most_chnl_attr_number_of_stream_buffers.attr, &most_chnl_attr_size_of_packet_buffer.attr, &most_chnl_attr_size_of_stream_buffer.attr, &most_chnl_attr_set_number_of_buffers.attr, &most_chnl_attr_set_buffer_size.attr, &most_chnl_attr_set_direction.attr, &most_chnl_attr_set_datatype.attr, &most_chnl_attr_set_subbuffer_size.attr, &most_chnl_attr_set_packets_per_xact.attr, &most_chnl_attr_channel_starving.attr, NULL, }; static struct kobj_type most_channel_ktype = { .sysfs_ops = &most_channel_sysfs_ops, .release = most_channel_release, .default_attrs = most_channel_def_attrs, }; static struct kset *most_channel_kset; /** * create_most_c_obj - allocates a channel object * @name: name of the channel object * @parent: parent kobject * * This create a channel object and registers it with sysfs. * Returns a pointer to the object or NULL when something went wrong. */ static struct most_c_obj * create_most_c_obj(const char *name, struct kobject *parent) { struct most_c_obj *c; int retval; c = kzalloc(sizeof(*c), GFP_KERNEL); if (!c) return NULL; c->kobj.kset = most_channel_kset; retval = kobject_init_and_add(&c->kobj, &most_channel_ktype, parent, "%s", name); if (retval) { kobject_put(&c->kobj); return NULL; } kobject_uevent(&c->kobj, KOBJ_ADD); return c; } /* ___ ___ * ___I N S T A N C E___ */ #define MOST_INST_ATTR(_name, _mode, _show, _store) \ struct most_inst_attribute most_inst_attr_##_name = \ __ATTR(_name, _mode, _show, _store) static struct list_head instance_list; /** * struct most_inst_attribute - to access the attributes of instance object * @attr: attributes of an instance * @show: pointer to the show function * @store: pointer to the store function */ struct most_inst_attribute { struct attribute attr; ssize_t (*show)(struct most_inst_obj *d, struct most_inst_attribute *attr, char *buf); ssize_t (*store)(struct most_inst_obj *d, struct most_inst_attribute *attr, const char *buf, size_t count); }; #define to_instance_attr(a) \ container_of(a, struct most_inst_attribute, attr) /** * instance_attr_show - show function for an instance object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer */ static ssize_t instance_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct most_inst_attribute *instance_attr; struct most_inst_obj *instance_obj; instance_attr = to_instance_attr(attr); instance_obj = to_inst_obj(kobj); if (!instance_attr->show) return -EIO; return instance_attr->show(instance_obj, instance_attr, buf); } /** * instance_attr_store - store function for an instance object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer * @len: length of buffer */ static ssize_t instance_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct most_inst_attribute *instance_attr; struct most_inst_obj *instance_obj; instance_attr = to_instance_attr(attr); instance_obj = to_inst_obj(kobj); if (!instance_attr->store) return -EIO; return instance_attr->store(instance_obj, instance_attr, buf, len); } static const struct sysfs_ops most_inst_sysfs_ops = { .show = instance_attr_show, .store = instance_attr_store, }; /** * most_inst_release - release function for instance object * @kobj: pointer to instance's kobject * * This frees the allocated memory for the instance object */ static void most_inst_release(struct kobject *kobj) { struct most_inst_obj *inst = to_inst_obj(kobj); kfree(inst); } static ssize_t show_description(struct most_inst_obj *instance_obj, struct most_inst_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", instance_obj->iface->description); } static ssize_t show_interface(struct most_inst_obj *instance_obj, struct most_inst_attribute *attr, char *buf) { switch (instance_obj->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"); } #define create_inst_attribute(value) \ static MOST_INST_ATTR(value, S_IRUGO, show_##value, NULL) create_inst_attribute(description); create_inst_attribute(interface); static struct attribute *most_inst_def_attrs[] = { &most_inst_attr_description.attr, &most_inst_attr_interface.attr, NULL, }; static struct kobj_type most_inst_ktype = { .sysfs_ops = &most_inst_sysfs_ops, .release = most_inst_release, .default_attrs = most_inst_def_attrs, }; static struct kset *most_inst_kset; /** * create_most_inst_obj - creates an instance object * @name: name of the object to be created * * This allocates memory for an instance structure, assigns the proper kset * and registers it with sysfs. * * Returns a pointer to the instance object or NULL when something went wrong. */ static struct most_inst_obj *create_most_inst_obj(const char *name) { struct most_inst_obj *inst; int retval; inst = kzalloc(sizeof(*inst), GFP_KERNEL); if (!inst) return NULL; inst->kobj.kset = most_inst_kset; retval = kobject_init_and_add(&inst->kobj, &most_inst_ktype, NULL, "%s", name); if (retval) { kobject_put(&inst->kobj); return NULL; } kobject_uevent(&inst->kobj, KOBJ_ADD); return inst; } /** * destroy_most_inst_obj - MOST instance release function * @inst: pointer to the instance object * * This decrements the reference counter of the instance object. * If the reference count turns zero, its release function is called */ static void destroy_most_inst_obj(struct most_inst_obj *inst) { struct most_c_obj *c, *tmp; list_for_each_entry_safe(c, tmp, &inst->channel_list, list) { flush_trash_fifo(c); flush_channel_fifos(c); kobject_put(&c->kobj); } kobject_put(&inst->kobj); } /* ___ ___ * ___A I M___ */ struct most_aim_obj { struct kobject kobj; struct list_head list; struct most_aim *driver; char add_link[STRING_SIZE]; char remove_link[STRING_SIZE]; }; #define to_aim_obj(d) container_of(d, struct most_aim_obj, kobj) static struct list_head aim_list; /** * struct most_aim_attribute - to access the attributes of AIM object * @attr: attributes of an AIM * @show: pointer to the show function * @store: pointer to the store function */ struct most_aim_attribute { struct attribute attr; ssize_t (*show)(struct most_aim_obj *d, struct most_aim_attribute *attr, char *buf); ssize_t (*store)(struct most_aim_obj *d, struct most_aim_attribute *attr, const char *buf, size_t count); }; #define to_aim_attr(a) container_of(a, struct most_aim_attribute, attr) /** * aim_attr_show - show function of an AIM object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer */ static ssize_t aim_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct most_aim_attribute *aim_attr; struct most_aim_obj *aim_obj; aim_attr = to_aim_attr(attr); aim_obj = to_aim_obj(kobj); if (!aim_attr->show) return -EIO; return aim_attr->show(aim_obj, aim_attr, buf); } /** * aim_attr_store - store function of an AIM object * @kobj: pointer to kobject * @attr: pointer to attribute struct * @buf: buffer * @len: length of buffer */ static ssize_t aim_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct most_aim_attribute *aim_attr; struct most_aim_obj *aim_obj; aim_attr = to_aim_attr(attr); aim_obj = to_aim_obj(kobj); if (!aim_attr->store) return -EIO; return aim_attr->store(aim_obj, aim_attr, buf, len); } static const struct sysfs_ops most_aim_sysfs_ops = { .show = aim_attr_show, .store = aim_attr_store, }; /** * most_aim_release - AIM release function * @kobj: pointer to AIM's kobject */ static void most_aim_release(struct kobject *kobj) { struct most_aim_obj *aim_obj = to_aim_obj(kobj); kfree(aim_obj); } static ssize_t show_add_link(struct most_aim_obj *aim_obj, struct most_aim_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", aim_obj->add_link); } /** * split_string - parses and changes string in the buffer buf and * splits it into two mandatory and one optional substrings. * * @buf: complete string from attribute 'add_channel' * @a: address of pointer to 1st substring (=instance name) * @b: address of pointer to 2nd substring (=channel name) * @c: optional address of pointer to 3rd substring (=user defined name) * * Examples: * * Input: "mdev0:ch0@ep_81:my_channel\n" or * "mdev0:ch0@ep_81:my_channel" * * Output: *a -> "mdev0", *b -> "ch0@ep_81", *c -> "my_channel" * * Input: "mdev0:ch0@ep_81\n" * Output: *a -> "mdev0", *b -> "ch0@ep_81", *c -> "" * * Input: "mdev0:ch0@ep_81" * Output: *a -> "mdev0", *b -> "ch0@ep_81", *c == NULL */ static int split_string(char *buf, char **a, char **b, char **c) { *a = strsep(&buf, ":"); if (!*a) return -EIO; *b = strsep(&buf, ":\n"); if (!*b) return -EIO; if (c) *c = strsep(&buf, ":\n"); return 0; } /** * get_channel_by_name - get pointer to channel object * @mdev: name of the device instance * @mdev_ch: name of the respective channel * * This retrieves the pointer to a channel object. */ static struct most_c_obj *get_channel_by_name(char *mdev, char *mdev_ch) { struct most_c_obj *c, *tmp; struct most_inst_obj *i, *i_tmp; int found = 0; list_for_each_entry_safe(i, i_tmp, &instance_list, list) { if (!strcmp(kobject_name(&i->kobj), mdev)) { found++; break; } } if (unlikely(!found)) return ERR_PTR(-EIO); list_for_each_entry_safe(c, tmp, &i->channel_list, list) { if (!strcmp(kobject_name(&c->kobj), mdev_ch)) { found++; break; } } if (unlikely(found < 2)) return ERR_PTR(-EIO); return c; } /** * store_add_link - store() function for add_link attribute * @aim_obj: pointer to AIM object * @attr: its attributes * @buf: buffer * @len: buffer length * * This parses the string given by buf and splits it into * three substrings. Note: third substring is optional. In case a cdev * AIM is loaded the optional 3rd 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 a pair of device and channel and probes the AIM * * Example: * (1) echo -n -e "mdev0:ch0@ep_81:my_rxchannel\n" >add_link * (2) echo -n -e "mdev0:ch0@ep_81\n" >add_link * * (1) would create the device node /dev/my_rxchannel * (2) would create the device node /dev/mdev0-ch0@ep_81 */ static ssize_t store_add_link(struct most_aim_obj *aim_obj, struct most_aim_attribute *attr, const char *buf, size_t len) { struct most_c_obj *c; struct most_aim **aim_ptr; char buffer[STRING_SIZE]; char *mdev; char *mdev_ch; char *mdev_devnod; char devnod_buf[STRING_SIZE]; int ret; size_t max_len = min_t(size_t, len + 1, STRING_SIZE); strlcpy(buffer, buf, max_len); strlcpy(aim_obj->add_link, buf, max_len); ret = split_string(buffer, &mdev, &mdev_ch, &mdev_devnod); if (ret) return ret; if (!mdev_devnod || *mdev_devnod == 0) { snprintf(devnod_buf, sizeof(devnod_buf), "%s-%s", mdev, mdev_ch); mdev_devnod = devnod_buf; } c = get_channel_by_name(mdev, mdev_ch); if (IS_ERR(c)) return -ENODEV; if (!c->aim0.ptr) aim_ptr = &c->aim0.ptr; else if (!c->aim1.ptr) aim_ptr = &c->aim1.ptr; else return -ENOSPC; *aim_ptr = aim_obj->driver; ret = aim_obj->driver->probe_channel(c->iface, c->channel_id, &c->cfg, &c->kobj, mdev_devnod); if (ret) { *aim_ptr = NULL; return ret; } return len; } static struct most_aim_attribute most_aim_attr_add_link = __ATTR(add_link, S_IRUGO | S_IWUSR, show_add_link, store_add_link); static ssize_t show_remove_link(struct most_aim_obj *aim_obj, struct most_aim_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", aim_obj->remove_link); } /** * store_remove_link - store function for remove_link attribute * @aim_obj: pointer to AIM object * @attr: its attributes * @buf: buffer * @len: buffer length * * Example: * echo -n -e "mdev0:ch0@ep_81\n" >remove_link */ static ssize_t store_remove_link(struct most_aim_obj *aim_obj, struct most_aim_attribute *attr, const char *buf, size_t len) { struct most_c_obj *c; char buffer[STRING_SIZE]; char *mdev; char *mdev_ch; int ret; size_t max_len = min_t(size_t, len + 1, STRING_SIZE); strlcpy(buffer, buf, max_len); strlcpy(aim_obj->remove_link, buf, max_len); ret = split_string(buffer, &mdev, &mdev_ch, NULL); if (ret) return ret; c = get_channel_by_name(mdev, mdev_ch); if (IS_ERR(c)) return -ENODEV; if (aim_obj->driver->disconnect_channel(c->iface, c->channel_id)) return -EIO; if (c->aim0.ptr == aim_obj->driver) c->aim0.ptr = NULL; if (c->aim1.ptr == aim_obj->driver) c->aim1.ptr = NULL; return len; } static struct most_aim_attribute most_aim_attr_remove_link = __ATTR(remove_link, S_IRUGO | S_IWUSR, show_remove_link, store_remove_link); static struct attribute *most_aim_def_attrs[] = { &most_aim_attr_add_link.attr, &most_aim_attr_remove_link.attr, NULL, }; static struct kobj_type most_aim_ktype = { .sysfs_ops = &most_aim_sysfs_ops, .release = most_aim_release, .default_attrs = most_aim_def_attrs, }; static struct kset *most_aim_kset; /** * create_most_aim_obj - creates an AIM object * @name: name of the AIM * * This creates an AIM object assigns the proper kset and registers * it with sysfs. * Returns a pointer to the object or NULL if something went wrong. */ static struct most_aim_obj *create_most_aim_obj(const char *name) { struct most_aim_obj *most_aim; int retval; most_aim = kzalloc(sizeof(*most_aim), GFP_KERNEL); if (!most_aim) return NULL; most_aim->kobj.kset = most_aim_kset; retval = kobject_init_and_add(&most_aim->kobj, &most_aim_ktype, NULL, "%s", name); if (retval) { kobject_put(&most_aim->kobj); return NULL; } kobject_uevent(&most_aim->kobj, KOBJ_ADD); return most_aim; } /** * destroy_most_aim_obj - AIM release function * @p: pointer to AIM object * * This decrements the reference counter of the AIM object. If the * reference count turns zero, its release function will be called. */ static void destroy_most_aim_obj(struct most_aim_obj *p) { kobject_put(&p->kobj); } /* ___ ___ * ___C O R E___ */ /** * Instantiation of the MOST bus */ static struct bus_type most_bus = { .name = "most", }; /** * Instantiation of the core driver */ static struct device_driver mostcore = { .name = "mostcore", .bus = &most_bus, }; static inline void trash_mbo(struct mbo *mbo) { unsigned long flags; struct most_c_obj *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 struct mbo *get_hdm_mbo(struct most_c_obj *c) { unsigned long flags; struct mbo *mbo; spin_lock_irqsave(&c->fifo_lock, flags); if (c->enqueue_halt || list_empty(&c->halt_fifo)) mbo = NULL; else mbo = list_pop_mbo(&c->halt_fifo); spin_unlock_irqrestore(&c->fifo_lock, flags); return mbo; } static void nq_hdm_mbo(struct mbo *mbo) { unsigned long flags; struct most_c_obj *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_c_obj *c = data; struct mbo *mbo; typeof(c->iface->enqueue) enqueue = c->iface->enqueue; while (likely(!kthread_should_stop())) { wait_event_interruptible(c->hdm_fifo_wq, (mbo = get_hdm_mbo(c)) || kthread_should_stop()); if (unlikely(!mbo)) continue; if (c->cfg.direction == MOST_CH_RX) mbo->buffer_length = c->cfg.buffer_size; if (unlikely(enqueue(mbo->ifp, mbo->hdm_channel_id, mbo))) { 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_c_obj *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: buffer object * * 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 AIM. */ static void arm_mbo(struct mbo *mbo) { unsigned long flags; struct most_c_obj *c; BUG_ON((!mbo) || (!mbo->context)); 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->aim0.refs && c->aim0.ptr->tx_completion) c->aim0.ptr->tx_completion(c->iface, c->channel_id); if (c->aim1.refs && c->aim1.ptr->tx_completion) c->aim1.ptr->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_c_obj *c, int dir, void (*compl)(struct mbo *)) { unsigned int i; int retval; struct mbo *mbo; 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) { pr_info("WARN: Allocation of MBO failed.\n"); retval = i; goto _exit; } mbo->context = c; mbo->ifp = c->iface; mbo->hdm_channel_id = c->channel_id; mbo->virt_address = dma_alloc_coherent(NULL, coherent_buf_size, &mbo->bus_address, GFP_KERNEL); if (!mbo->virt_address) { pr_info("WARN: No DMA coherent buffer.\n"); retval = i; goto _error1; } 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 { arm_mbo(mbo); } } return i; _error1: kfree(mbo); _exit: return retval; } /** * most_submit_mbo - submits an MBO to fifo * @mbo: pointer to the MBO * */ int most_submit_mbo(struct mbo *mbo) { if (unlikely((!mbo) || (!mbo->context))) { pr_err("Bad MBO or missing channel reference\n"); return -EINVAL; } nq_hdm_mbo(mbo); return 0; } EXPORT_SYMBOL_GPL(most_submit_mbo); /** * most_write_completion - write completion handler * @mbo: pointer to MBO * * 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_c_obj *c; BUG_ON((!mbo) || (!mbo->context)); 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); } /** * get_channel_by_iface - get pointer to channel object * @iface: pointer to interface instance * @id: channel ID * * This retrieves a pointer to a channel of the given interface and channel ID. */ static struct most_c_obj *get_channel_by_iface(struct most_interface *iface, int id) { struct most_inst_obj *i; if (unlikely(!iface)) { pr_err("Bad interface\n"); return NULL; } if (unlikely((id < 0) || (id >= iface->num_channels))) { pr_err("Channel index (%d) out of range\n", id); return NULL; } i = iface->priv; if (unlikely(!i)) { pr_err("interface is not registered\n"); return NULL; } return i->channel[id]; } int channel_has_mbo(struct most_interface *iface, int id, struct most_aim *aim) { struct most_c_obj *c = get_channel_by_iface(iface, id); unsigned long flags; int empty; if (unlikely(!c)) return -EINVAL; if (c->aim0.refs && c->aim1.refs && ((aim == c->aim0.ptr && c->aim0.num_buffers <= 0) || (aim == c->aim1.ptr && c->aim1.num_buffers <= 0))) return false; 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 * * 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 most_aim *aim) { struct mbo *mbo; struct most_c_obj *c; unsigned long flags; int *num_buffers_ptr; c = get_channel_by_iface(iface, id); if (unlikely(!c)) return NULL; if (c->aim0.refs && c->aim1.refs && ((aim == c->aim0.ptr && c->aim0.num_buffers <= 0) || (aim == c->aim1.ptr && c->aim1.num_buffers <= 0))) return NULL; if (aim == c->aim0.ptr) num_buffers_ptr = &c->aim0.num_buffers; else if (aim == c->aim1.ptr) num_buffers_ptr = &c->aim1.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: buffer object */ void most_put_mbo(struct mbo *mbo) { struct most_c_obj *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: pointer to MBO * * 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 AIM for further processing. */ static void most_read_completion(struct mbo *mbo) { struct most_c_obj *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)) { pr_info("WARN: rx device out of buffers\n"); c->is_starving = 1; } if (c->aim0.refs && c->aim0.ptr->rx_completion && c->aim0.ptr->rx_completion(mbo) == 0) return; if (c->aim1.refs && c->aim1.ptr->rx_completion && c->aim1.ptr->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 * * 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 most_aim *aim) { int num_buffer; int ret; struct most_c_obj *c = get_channel_by_iface(iface, id); if (unlikely(!c)) return -EINVAL; mutex_lock(&c->start_mutex); if (c->aim0.refs + c->aim1.refs > 0) goto out; /* already started by other aim */ 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)) { pr_info("failed to allocate memory\n"); ret = -ENOMEM; goto error; } ret = run_enqueue_thread(c, id); if (ret) goto error; c->is_starving = 0; c->aim0.num_buffers = c->cfg.num_buffers / 2; c->aim1.num_buffers = c->cfg.num_buffers - c->aim0.num_buffers; atomic_set(&c->mbo_ref, num_buffer); out: if (aim == c->aim0.ptr) c->aim0.refs++; if (aim == c->aim1.ptr) c->aim1.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 */ int most_stop_channel(struct most_interface *iface, int id, struct most_aim *aim) { struct most_c_obj *c; if (unlikely((!iface) || (id >= iface->num_channels) || (id < 0))) { pr_err("Bad interface or index out of range\n"); return -EINVAL; } c = get_channel_by_iface(iface, id); if (unlikely(!c)) return -EINVAL; mutex_lock(&c->start_mutex); if (c->aim0.refs + c->aim1.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 (aim == c->aim0.ptr) c->aim0.refs--; if (aim == c->aim1.ptr) c->aim1.refs--; mutex_unlock(&c->start_mutex); return 0; } EXPORT_SYMBOL_GPL(most_stop_channel); /** * most_register_aim - registers an AIM (driver) with the core * @aim: instance of AIM to be registered */ int most_register_aim(struct most_aim *aim) { struct most_aim_obj *aim_obj; if (!aim) { pr_err("Bad driver\n"); return -EINVAL; } aim_obj = create_most_aim_obj(aim->name); if (!aim_obj) { pr_info("failed to alloc driver object\n"); return -ENOMEM; } aim_obj->driver = aim; aim->context = aim_obj; pr_info("registered new application interfacing module %s\n", aim->name); list_add_tail(&aim_obj->list, &aim_list); return 0; } EXPORT_SYMBOL_GPL(most_register_aim); /** * most_deregister_aim - deregisters an AIM (driver) with the core * @aim: AIM to be removed */ int most_deregister_aim(struct most_aim *aim) { struct most_aim_obj *aim_obj; struct most_c_obj *c, *tmp; struct most_inst_obj *i, *i_tmp; if (!aim) { pr_err("Bad driver\n"); return -EINVAL; } aim_obj = aim->context; if (!aim_obj) { pr_info("driver not registered.\n"); return -EINVAL; } list_for_each_entry_safe(i, i_tmp, &instance_list, list) { list_for_each_entry_safe(c, tmp, &i->channel_list, list) { if (c->aim0.ptr == aim || c->aim1.ptr == aim) aim->disconnect_channel( c->iface, c->channel_id); if (c->aim0.ptr == aim) c->aim0.ptr = NULL; if (c->aim1.ptr == aim) c->aim1.ptr = NULL; } } list_del(&aim_obj->list); destroy_most_aim_obj(aim_obj); pr_info("deregistering application interfacing module %s\n", aim->name); return 0; } EXPORT_SYMBOL_GPL(most_deregister_aim); /** * most_register_interface - registers an interface with core * @iface: pointer to the instance of the interface description. * * Allocates and initializes a new interface instance and all of its channels. * Returns a pointer to kobject or an error pointer. */ struct kobject *most_register_interface(struct most_interface *iface) { unsigned int i; int id; char name[STRING_SIZE]; char channel_name[STRING_SIZE]; struct most_c_obj *c; struct most_inst_obj *inst; if (!iface || !iface->enqueue || !iface->configure || !iface->poison_channel || (iface->num_channels > MAX_CHANNELS)) { pr_err("Bad interface or channel overflow\n"); return ERR_PTR(-EINVAL); } id = ida_simple_get(&mdev_id, 0, 0, GFP_KERNEL); if (id < 0) { pr_info("Failed to alloc mdev ID\n"); return ERR_PTR(id); } snprintf(name, STRING_SIZE, "mdev%d", id); inst = create_most_inst_obj(name); if (!inst) { pr_info("Failed to allocate interface instance\n"); ida_simple_remove(&mdev_id, id); return ERR_PTR(-ENOMEM); } iface->priv = inst; INIT_LIST_HEAD(&inst->channel_list); inst->iface = iface; inst->dev_id = id; list_add_tail(&inst->list, &instance_list); for (i = 0; i < iface->num_channels; i++) { const char *name_suffix = iface->channel_vector[i].name_suffix; if (!name_suffix) snprintf(channel_name, STRING_SIZE, "ch%d", i); else if (name_suffix[0] == '@') snprintf(channel_name, STRING_SIZE, "ch%d%s", i, name_suffix); else snprintf(channel_name, STRING_SIZE, "%s", name_suffix); /* this increments the reference count of this instance */ c = create_most_c_obj(channel_name, &inst->kobj); if (!c) goto free_instance; inst->channel[i] = c; c->is_starving = 0; c->iface = iface; c->inst = inst; 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); list_add_tail(&c->list, &inst->channel_list); } pr_info("registered new MOST device mdev%d (%s)\n", inst->dev_id, iface->description); return &inst->kobj; free_instance: pr_info("Failed allocate channel(s)\n"); list_del(&inst->list); ida_simple_remove(&mdev_id, id); destroy_most_inst_obj(inst); return ERR_PTR(-ENOMEM); } EXPORT_SYMBOL_GPL(most_register_interface); /** * most_deregister_interface - deregisters an interface with core * @iface: pointer to the interface instance description. * * Before removing an interface instance from the list, all running * channels are stopped and poisoned. */ void most_deregister_interface(struct most_interface *iface) { struct most_inst_obj *i = iface->priv; struct most_c_obj *c; if (unlikely(!i)) { pr_info("Bad Interface\n"); return; } pr_info("deregistering MOST device %s (%s)\n", i->kobj.name, iface->description); list_for_each_entry(c, &i->channel_list, list) { if (c->aim0.ptr) c->aim0.ptr->disconnect_channel(c->iface, c->channel_id); if (c->aim1.ptr) c->aim1.ptr->disconnect_channel(c->iface, c->channel_id); c->aim0.ptr = NULL; c->aim1.ptr = NULL; } ida_simple_remove(&mdev_id, i->dev_id); list_del(&i->list); destroy_most_inst_obj(i); } 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_c_obj *c = get_channel_by_iface(iface, id); if (likely(c)) c->enqueue_halt = true; } 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_c_obj *c = get_channel_by_iface(iface, id); if (unlikely(!c)) return; c->enqueue_halt = false; wake_up_interruptible(&c->hdm_fifo_wq); } EXPORT_SYMBOL_GPL(most_resume_enqueue); static int __init most_init(void) { pr_info("init()\n"); INIT_LIST_HEAD(&instance_list); INIT_LIST_HEAD(&aim_list); ida_init(&mdev_id); if (bus_register(&most_bus)) { pr_info("Cannot register most bus\n"); goto exit; } most_class = class_create(THIS_MODULE, "most"); if (IS_ERR(most_class)) { pr_info("No udev support.\n"); goto exit_bus; } if (driver_register(&mostcore)) { pr_info("Cannot register core driver\n"); goto exit_class; } class_glue_dir = device_create(most_class, NULL, 0, NULL, "mostcore"); if (!class_glue_dir) goto exit_driver; most_aim_kset = kset_create_and_add("aims", NULL, &class_glue_dir->kobj); if (!most_aim_kset) goto exit_class_container; most_inst_kset = kset_create_and_add("devices", NULL, &class_glue_dir->kobj); if (!most_inst_kset) goto exit_driver_kset; return 0; exit_driver_kset: kset_unregister(most_aim_kset); exit_class_container: device_destroy(most_class, 0); exit_driver: driver_unregister(&mostcore); exit_class: class_destroy(most_class); exit_bus: bus_unregister(&most_bus); exit: return -ENOMEM; } static void __exit most_exit(void) { struct most_inst_obj *i, *i_tmp; struct most_aim_obj *d, *d_tmp; pr_info("exit core module\n"); list_for_each_entry_safe(d, d_tmp, &aim_list, list) { destroy_most_aim_obj(d); } list_for_each_entry_safe(i, i_tmp, &instance_list, list) { list_del(&i->list); destroy_most_inst_obj(i); } kset_unregister(most_inst_kset); kset_unregister(most_aim_kset); device_destroy(most_class, 0); driver_unregister(&mostcore); class_destroy(most_class); bus_unregister(&most_bus); ida_destroy(&mdev_id); } module_init(most_init); module_exit(most_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Christian Gromm "); MODULE_DESCRIPTION("Core module of stacked MOST Linux driver");