/* Copyright (c) 2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "coresight-priv.h" #define NO_SINK (-1) static int curr_sink = NO_SINK; static LIST_HEAD(coresight_orph_conns); static LIST_HEAD(coresight_devs); static DEFINE_SEMAPHORE(coresight_mutex); static int coresight_find_link_inport(struct coresight_device *csdev) { int i; struct coresight_device *parent; struct coresight_connection *conn; parent = container_of(csdev->path_link.next, struct coresight_device, path_link); for (i = 0; i < parent->nr_conns; i++) { conn = &parent->conns[i]; if (conn->child_dev == csdev) return conn->child_port; } pr_err("coresight: couldn't find inport, parent: %d, child: %d\n", parent->id, csdev->id); return 0; } static int coresight_find_link_outport(struct coresight_device *csdev) { int i; struct coresight_device *child; struct coresight_connection *conn; child = container_of(csdev->path_link.prev, struct coresight_device, path_link); for (i = 0; i < csdev->nr_conns; i++) { conn = &csdev->conns[i]; if (conn->child_dev == child) return conn->outport; } pr_err("coresight: couldn't find outport, parent: %d, child: %d\n", csdev->id, child->id); return 0; } static int coresight_enable_sink(struct coresight_device *csdev) { int ret; if (csdev->refcnt.sink_refcnt == 0) { if (csdev->ops->sink_ops->enable) { ret = csdev->ops->sink_ops->enable(csdev); if (ret) goto err; csdev->enable = true; } } csdev->refcnt.sink_refcnt++; return 0; err: return ret; } static void coresight_disable_sink(struct coresight_device *csdev) { if (csdev->refcnt.sink_refcnt == 1) { if (csdev->ops->sink_ops->disable) { csdev->ops->sink_ops->disable(csdev); csdev->enable = false; } } csdev->refcnt.sink_refcnt--; } static int coresight_enable_link(struct coresight_device *csdev) { int ret; int link_subtype; int refport, inport, outport; inport = coresight_find_link_inport(csdev); outport = coresight_find_link_outport(csdev); link_subtype = csdev->subtype.link_subtype; if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) refport = inport; else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) refport = outport; else refport = 0; if (csdev->refcnt.link_refcnts[refport] == 0) { if (csdev->ops->link_ops->enable) { ret = csdev->ops->link_ops->enable(csdev, inport, outport); if (ret) goto err; csdev->enable = true; } } csdev->refcnt.link_refcnts[refport]++; return 0; err: return ret; } static void coresight_disable_link(struct coresight_device *csdev) { int link_subtype; int refport, inport, outport; inport = coresight_find_link_inport(csdev); outport = coresight_find_link_outport(csdev); link_subtype = csdev->subtype.link_subtype; if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) refport = inport; else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) refport = outport; else refport = 0; if (csdev->refcnt.link_refcnts[refport] == 1) { if (csdev->ops->link_ops->disable) { csdev->ops->link_ops->disable(csdev, inport, outport); csdev->enable = false; } } csdev->refcnt.link_refcnts[refport]--; } static int coresight_enable_source(struct coresight_device *csdev) { int ret; if (csdev->refcnt.source_refcnt == 0) { if (csdev->ops->source_ops->enable) { ret = csdev->ops->source_ops->enable(csdev); if (ret) goto err; csdev->enable = true; } } csdev->refcnt.source_refcnt++; return 0; err: return ret; } static void coresight_disable_source(struct coresight_device *csdev) { if (csdev->refcnt.source_refcnt == 1) { if (csdev->ops->source_ops->disable) { csdev->ops->source_ops->disable(csdev); csdev->enable = false; } } csdev->refcnt.source_refcnt--; } static struct list_head *coresight_build_path(struct coresight_device *csdev, struct list_head *path) { int i; struct list_head *p; struct coresight_connection *conn; if (csdev->id == curr_sink) { list_add_tail(&csdev->path_link, path); return path; } for (i = 0; i < csdev->nr_conns; i++) { conn = &csdev->conns[i]; p = coresight_build_path(conn->child_dev, path); if (p) { list_add_tail(&csdev->path_link, p); return p; } } return NULL; } static void coresight_release_path(struct list_head *path) { struct coresight_device *cd, *temp; list_for_each_entry_safe(cd, temp, path, path_link) list_del(&cd->path_link); } static int coresight_enable_path(struct list_head *path, bool incl_source) { int ret = 0; struct coresight_device *cd; list_for_each_entry(cd, path, path_link) { if (cd == list_first_entry(path, struct coresight_device, path_link)) { ret = coresight_enable_sink(cd); } else if (list_is_last(&cd->path_link, path)) { if (incl_source) ret = coresight_enable_source(cd); } else { ret = coresight_enable_link(cd); } if (ret) goto err; } return 0; err: list_for_each_entry_continue_reverse(cd, path, path_link) { if (cd == list_first_entry(path, struct coresight_device, path_link)) { coresight_disable_sink(cd); } else if (list_is_last(&cd->path_link, path)) { if (incl_source) coresight_disable_source(cd); } else { coresight_disable_link(cd); } } return ret; } static void coresight_disable_path(struct list_head *path, bool incl_source) { struct coresight_device *cd; list_for_each_entry(cd, path, path_link) { if (cd == list_first_entry(path, struct coresight_device, path_link)) { coresight_disable_sink(cd); } else if (list_is_last(&cd->path_link, path)) { if (incl_source) coresight_disable_source(cd); } else { coresight_disable_link(cd); } } } static int coresight_switch_sink(struct coresight_device *csdev) { int ret = 0; LIST_HEAD(path); struct coresight_device *cd; if (IS_ERR_OR_NULL(csdev)) return -EINVAL; down(&coresight_mutex); if (csdev->id == curr_sink) goto out; list_for_each_entry(cd, &coresight_devs, dev_link) { if (cd->type == CORESIGHT_DEV_TYPE_SOURCE && cd->enable) { coresight_build_path(cd, &path); coresight_disable_path(&path, false); coresight_release_path(&path); } } curr_sink = csdev->id; list_for_each_entry(cd, &coresight_devs, dev_link) { if (cd->type == CORESIGHT_DEV_TYPE_SOURCE && cd->enable) { coresight_build_path(cd, &path); ret = coresight_enable_path(&path, false); coresight_release_path(&path); if (ret) goto err; } } out: up(&coresight_mutex); return 0; err: list_for_each_entry(cd, &coresight_devs, dev_link) { if (cd->type == CORESIGHT_DEV_TYPE_SOURCE && cd->enable) coresight_disable_source(cd); } pr_err("coresight: sink switch failed, sources disabled; try again\n"); return ret; } int coresight_enable(struct coresight_device *csdev) { int ret = 0; LIST_HEAD(path); if (IS_ERR_OR_NULL(csdev)) return -EINVAL; down(&coresight_mutex); if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) { ret = -EINVAL; pr_err("coresight: wrong device type in %s\n", __func__); goto out; } if (csdev->enable) goto out; coresight_build_path(csdev, &path); ret = coresight_enable_path(&path, true); coresight_release_path(&path); if (ret) pr_err("coresight: enable failed\n"); out: up(&coresight_mutex); return ret; } EXPORT_SYMBOL_GPL(coresight_enable); void coresight_disable(struct coresight_device *csdev) { LIST_HEAD(path); if (IS_ERR_OR_NULL(csdev)) return; down(&coresight_mutex); if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) { pr_err("coresight: wrong device type in %s\n", __func__); goto out; } if (!csdev->enable) goto out; coresight_build_path(csdev, &path); coresight_disable_path(&path, true); coresight_release_path(&path); out: up(&coresight_mutex); } EXPORT_SYMBOL_GPL(coresight_disable); void coresight_abort(void) { struct coresight_device *cd; if (down_trylock(&coresight_mutex)) { pr_err("coresight: abort could not be processed\n"); return; } if (curr_sink == NO_SINK) goto out; list_for_each_entry(cd, &coresight_devs, dev_link) { if (cd->id == curr_sink) { if (cd->enable && cd->ops->sink_ops->abort) { cd->ops->sink_ops->abort(cd); cd->enable = false; } } } out: up(&coresight_mutex); } EXPORT_SYMBOL_GPL(coresight_abort); static ssize_t coresight_show_type(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", dev->type->name); } static struct device_attribute coresight_dev_attrs[] = { __ATTR(type, S_IRUGO, coresight_show_type, NULL), { }, }; struct bus_type coresight_bus_type = { .name = "coresight", .dev_attrs = coresight_dev_attrs, }; static ssize_t coresight_show_curr_sink(struct device *dev, struct device_attribute *attr, char *buf) { struct coresight_device *csdev = to_coresight_device(dev); return scnprintf(buf, PAGE_SIZE, "%u\n", csdev->id == curr_sink ? 1 : 0); } static ssize_t coresight_store_curr_sink(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int ret = 0; unsigned long val; struct coresight_device *csdev = to_coresight_device(dev); if (sscanf(buf, "%lx", &val) != 1) return -EINVAL; if (val) ret = coresight_switch_sink(csdev); else ret = -EINVAL; if (ret) return ret; return size; } static DEVICE_ATTR(curr_sink, S_IRUGO | S_IWUSR, coresight_show_curr_sink, coresight_store_curr_sink); static ssize_t coresight_show_enable(struct device *dev, struct device_attribute *attr, char *buf) { struct coresight_device *csdev = to_coresight_device(dev); return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->enable); } static ssize_t coresight_store_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int ret = 0; unsigned long val; struct coresight_device *csdev = to_coresight_device(dev); if (sscanf(buf, "%lx", &val) != 1) return -EINVAL; if (val) ret = coresight_enable(csdev); else coresight_disable(csdev); if (ret) return ret; return size; } static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, coresight_show_enable, coresight_store_enable); static struct attribute *coresight_attrs_sink[] = { &dev_attr_curr_sink.attr, NULL, }; static struct attribute_group coresight_attr_grp_sink = { .attrs = coresight_attrs_sink, }; static const struct attribute_group *coresight_attr_grps_sink[] = { &coresight_attr_grp_sink, NULL, }; static struct attribute *coresight_attrs_source[] = { &dev_attr_enable.attr, NULL, }; static struct attribute_group coresight_attr_grp_source = { .attrs = coresight_attrs_source, }; static const struct attribute_group *coresight_attr_grps_source[] = { &coresight_attr_grp_source, NULL, }; static struct device_type coresight_dev_type[] = { { .name = "none", }, { .name = "sink", .groups = coresight_attr_grps_sink, }, { .name = "link", }, { .name = "linksink", .groups = coresight_attr_grps_sink, }, { .name = "source", .groups = coresight_attr_grps_source, }, }; static void coresight_device_release(struct device *dev) { struct coresight_device *csdev = to_coresight_device(dev); kfree(csdev); } static void coresight_fixup_orphan_conns(struct coresight_device *csdev) { struct coresight_connection *conn, *temp; list_for_each_entry_safe(conn, temp, &coresight_orph_conns, link) { if (conn->child_id == csdev->id) { conn->child_dev = csdev; list_del(&conn->link); } } } static void coresight_fixup_device_conns(struct coresight_device *csdev) { int i; struct coresight_device *cd; bool found; for (i = 0; i < csdev->nr_conns; i++) { found = false; list_for_each_entry(cd, &coresight_devs, dev_link) { if (csdev->conns[i].child_id == cd->id) { csdev->conns[i].child_dev = cd; found = true; break; } } if (!found) list_add_tail(&csdev->conns[i].link, &coresight_orph_conns); } } struct coresight_device *coresight_register(struct coresight_desc *desc) { int i; int ret; int link_subtype; int nr_refcnts; int *refcnts = NULL; struct coresight_device *csdev; struct coresight_connection *conns; if (IS_ERR_OR_NULL(desc)) return ERR_PTR(-EINVAL); csdev = kzalloc(sizeof(*csdev), GFP_KERNEL); if (!csdev) { ret = -ENOMEM; goto err_kzalloc_csdev; } csdev->id = desc->pdata->id; if (desc->type == CORESIGHT_DEV_TYPE_LINK || desc->type == CORESIGHT_DEV_TYPE_LINKSINK) { link_subtype = desc->subtype.link_subtype; if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) nr_refcnts = desc->pdata->nr_inports; else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) nr_refcnts = desc->pdata->nr_outports; else nr_refcnts = 1; refcnts = kzalloc(sizeof(*refcnts) * nr_refcnts, GFP_KERNEL); if (!refcnts) { ret = -ENOMEM; goto err_kzalloc_refcnts; } csdev->refcnt.link_refcnts = refcnts; } csdev->nr_conns = desc->pdata->nr_outports; conns = kzalloc(sizeof(*conns) * csdev->nr_conns, GFP_KERNEL); if (!conns) { ret = -ENOMEM; goto err_kzalloc_conns; } for (i = 0; i < csdev->nr_conns; i++) { conns[i].outport = desc->pdata->outports[i]; conns[i].child_id = desc->pdata->child_ids[i]; conns[i].child_port = desc->pdata->child_ports[i]; } csdev->conns = conns; csdev->type = desc->type; csdev->subtype = desc->subtype; csdev->ops = desc->ops; csdev->owner = desc->owner; csdev->dev.type = &coresight_dev_type[desc->type]; csdev->dev.groups = desc->groups; csdev->dev.parent = desc->dev; csdev->dev.bus = &coresight_bus_type; csdev->dev.release = coresight_device_release; dev_set_name(&csdev->dev, "%s", desc->pdata->name); down(&coresight_mutex); if (desc->pdata->default_sink) { if (curr_sink == NO_SINK) { curr_sink = csdev->id; } else { ret = -EINVAL; goto err_default_sink; } } coresight_fixup_device_conns(csdev); ret = device_register(&csdev->dev); if (ret) goto err_dev_reg; coresight_fixup_orphan_conns(csdev); list_add_tail(&csdev->dev_link, &coresight_devs); up(&coresight_mutex); return csdev; err_dev_reg: put_device(&csdev->dev); err_default_sink: up(&coresight_mutex); kfree(conns); err_kzalloc_conns: kfree(refcnts); err_kzalloc_refcnts: kfree(csdev); err_kzalloc_csdev: return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(coresight_register); void coresight_unregister(struct coresight_device *csdev) { if (IS_ERR_OR_NULL(csdev)) return; if (get_device(&csdev->dev)) { device_unregister(&csdev->dev); put_device(&csdev->dev); } } EXPORT_SYMBOL_GPL(coresight_unregister); static int __init coresight_init(void) { return bus_register(&coresight_bus_type); } subsys_initcall(coresight_init); static void __exit coresight_exit(void) { bus_unregister(&coresight_bus_type); } module_exit(coresight_exit); MODULE_LICENSE("GPL v2");