Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Uwe Kleine-König | 3572 | 99.97% | 4 | 80.00% |
Gavin Schenk | 1 | 0.03% | 1 | 20.00% |
Total | 3573 | 5 |
// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de> */ #include <linux/kernel.h> #include <linux/device.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/sysfs.h> #include "siox.h" /* * The lowest bit in the SIOX status word signals if the in-device watchdog is * ok. If the bit is set, the device is functional. * * On writing the watchdog timer is reset when this bit toggles. */ #define SIOX_STATUS_WDG 0x01 /* * Bits 1 to 3 of the status word read as the bitwise negation of what was * clocked in before. The value clocked in is changed in each cycle and so * allows to detect transmit/receive problems. */ #define SIOX_STATUS_COUNTER 0x0e /* * Each Siox-Device has a 4 bit type number that is neither 0 nor 15. This is * available in the upper nibble of the read status. * * On write these bits are DC. */ #define SIOX_STATUS_TYPE 0xf0 #define CREATE_TRACE_POINTS #include <trace/events/siox.h> static bool siox_is_registered; static void siox_master_lock(struct siox_master *smaster) { mutex_lock(&smaster->lock); } static void siox_master_unlock(struct siox_master *smaster) { mutex_unlock(&smaster->lock); } static inline u8 siox_status_clean(u8 status_read, u8 status_written) { /* * bits 3:1 of status sample the respective bit in the status * byte written in the previous cycle but inverted. So if you wrote the * status word as 0xa before (counter = 0b101), it is expected to get * back the counter bits as 0b010. * * So given the last status written this function toggles the there * unset counter bits in the read value such that the counter bits in * the return value are all zero iff the bits were read as expected to * simplify error detection. */ return status_read ^ (~status_written & 0xe); } static bool siox_device_counter_error(struct siox_device *sdevice, u8 status_clean) { return (status_clean & SIOX_STATUS_COUNTER) != 0; } static bool siox_device_type_error(struct siox_device *sdevice, u8 status_clean) { u8 statustype = (status_clean & SIOX_STATUS_TYPE) >> 4; /* * If the device knows which value the type bits should have, check * against this value otherwise just rule out the invalid values 0b0000 * and 0b1111. */ if (sdevice->statustype) { if (statustype != sdevice->statustype) return true; } else { switch (statustype) { case 0: case 0xf: return true; } } return false; } static bool siox_device_wdg_error(struct siox_device *sdevice, u8 status_clean) { return (status_clean & SIOX_STATUS_WDG) == 0; } /* * If there is a type or counter error the device is called "unsynced". */ bool siox_device_synced(struct siox_device *sdevice) { if (siox_device_type_error(sdevice, sdevice->status_read_clean)) return false; return !siox_device_counter_error(sdevice, sdevice->status_read_clean); } EXPORT_SYMBOL_GPL(siox_device_synced); /* * A device is called "connected" if it is synced and the watchdog is not * asserted. */ bool siox_device_connected(struct siox_device *sdevice) { if (!siox_device_synced(sdevice)) return false; return !siox_device_wdg_error(sdevice, sdevice->status_read_clean); } EXPORT_SYMBOL_GPL(siox_device_connected); static void siox_poll(struct siox_master *smaster) { struct siox_device *sdevice; size_t i = smaster->setbuf_len; unsigned int devno = 0; int unsync_error = 0; smaster->last_poll = jiffies; /* * The counter bits change in each second cycle, the watchdog bit * toggles each time. * The counter bits hold values from [0, 6]. 7 would be possible * theoretically but the protocol designer considered that a bad idea * for reasons unknown today. (Maybe that's because then the status read * back has only zeros in the counter bits then which might be confused * with a stuck-at-0 error. But for the same reason (with s/0/1/) 0 * could be skipped.) */ if (++smaster->status > 0x0d) smaster->status = 0; memset(smaster->buf, 0, smaster->setbuf_len); /* prepare data pushed out to devices in buf[0..setbuf_len) */ list_for_each_entry(sdevice, &smaster->devices, node) { struct siox_driver *sdriver = to_siox_driver(sdevice->dev.driver); sdevice->status_written = smaster->status; i -= sdevice->inbytes; /* * If the device or a previous one is unsynced, don't pet the * watchdog. This is done to ensure that the device is kept in * reset when something is wrong. */ if (!siox_device_synced(sdevice)) unsync_error = 1; if (sdriver && !unsync_error) sdriver->set_data(sdevice, sdevice->status_written, &smaster->buf[i + 1]); else /* * Don't trigger watchdog if there is no driver or a * sync problem */ sdevice->status_written &= ~SIOX_STATUS_WDG; smaster->buf[i] = sdevice->status_written; trace_siox_set_data(smaster, sdevice, devno, i); devno++; } smaster->pushpull(smaster, smaster->setbuf_len, smaster->buf, smaster->getbuf_len, smaster->buf + smaster->setbuf_len); unsync_error = 0; /* interpret data pulled in from devices in buf[setbuf_len..] */ devno = 0; i = smaster->setbuf_len; list_for_each_entry(sdevice, &smaster->devices, node) { struct siox_driver *sdriver = to_siox_driver(sdevice->dev.driver); u8 status = smaster->buf[i + sdevice->outbytes - 1]; u8 status_clean; u8 prev_status_clean = sdevice->status_read_clean; bool synced = true; bool connected = true; if (!siox_device_synced(sdevice)) unsync_error = 1; /* * If the watchdog bit wasn't toggled in this cycle, report the * watchdog as active to give a consistent view for drivers and * sysfs consumers. */ if (!sdriver || unsync_error) status &= ~SIOX_STATUS_WDG; status_clean = siox_status_clean(status, sdevice->status_written_lastcycle); /* Check counter and type bits */ if (siox_device_counter_error(sdevice, status_clean) || siox_device_type_error(sdevice, status_clean)) { bool prev_error; synced = false; /* only report a new error if the last cycle was ok */ prev_error = siox_device_counter_error(sdevice, prev_status_clean) || siox_device_type_error(sdevice, prev_status_clean); if (!prev_error) { sdevice->status_errors++; sysfs_notify_dirent(sdevice->status_errors_kn); } } /* If the device is unsynced report the watchdog as active */ if (!synced) { status &= ~SIOX_STATUS_WDG; status_clean &= ~SIOX_STATUS_WDG; } if (siox_device_wdg_error(sdevice, status_clean)) connected = false; /* The watchdog state changed just now */ if ((status_clean ^ prev_status_clean) & SIOX_STATUS_WDG) { sysfs_notify_dirent(sdevice->watchdog_kn); if (siox_device_wdg_error(sdevice, status_clean)) { struct kernfs_node *wd_errs = sdevice->watchdog_errors_kn; sdevice->watchdog_errors++; sysfs_notify_dirent(wd_errs); } } if (connected != sdevice->connected) sysfs_notify_dirent(sdevice->connected_kn); sdevice->status_read_clean = status_clean; sdevice->status_written_lastcycle = sdevice->status_written; sdevice->connected = connected; trace_siox_get_data(smaster, sdevice, devno, status_clean, i); /* only give data read to driver if the device is connected */ if (sdriver && connected) sdriver->get_data(sdevice, &smaster->buf[i]); devno++; i += sdevice->outbytes; } } static int siox_poll_thread(void *data) { struct siox_master *smaster = data; signed long timeout = 0; get_device(&smaster->dev); for (;;) { if (kthread_should_stop()) { put_device(&smaster->dev); return 0; } siox_master_lock(smaster); if (smaster->active) { unsigned long next_poll = smaster->last_poll + smaster->poll_interval; if (time_is_before_eq_jiffies(next_poll)) siox_poll(smaster); timeout = smaster->poll_interval - (jiffies - smaster->last_poll); } else { timeout = MAX_SCHEDULE_TIMEOUT; } /* * Set the task to idle while holding the lock. This makes sure * that we don't sleep too long when the bus is reenabled before * schedule_timeout is reached. */ if (timeout > 0) set_current_state(TASK_IDLE); siox_master_unlock(smaster); if (timeout > 0) schedule_timeout(timeout); /* * I'm not clear if/why it is important to set the state to * RUNNING again, but it fixes a "do not call blocking ops when * !TASK_RUNNING;"-warning. */ set_current_state(TASK_RUNNING); } } static int __siox_start(struct siox_master *smaster) { if (!(smaster->setbuf_len + smaster->getbuf_len)) return -ENODEV; if (!smaster->buf) return -ENOMEM; if (smaster->active) return 0; smaster->active = 1; wake_up_process(smaster->poll_thread); return 1; } static int siox_start(struct siox_master *smaster) { int ret; siox_master_lock(smaster); ret = __siox_start(smaster); siox_master_unlock(smaster); return ret; } static int __siox_stop(struct siox_master *smaster) { if (smaster->active) { struct siox_device *sdevice; smaster->active = 0; list_for_each_entry(sdevice, &smaster->devices, node) { if (sdevice->connected) sysfs_notify_dirent(sdevice->connected_kn); sdevice->connected = false; } return 1; } return 0; } static int siox_stop(struct siox_master *smaster) { int ret; siox_master_lock(smaster); ret = __siox_stop(smaster); siox_master_unlock(smaster); return ret; } static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); return sprintf(buf, "%s\n", sdev->type); } static DEVICE_ATTR_RO(type); static ssize_t inbytes_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); return sprintf(buf, "%zu\n", sdev->inbytes); } static DEVICE_ATTR_RO(inbytes); static ssize_t outbytes_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); return sprintf(buf, "%zu\n", sdev->outbytes); } static DEVICE_ATTR_RO(outbytes); static ssize_t status_errors_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); unsigned int status_errors; siox_master_lock(sdev->smaster); status_errors = sdev->status_errors; siox_master_unlock(sdev->smaster); return sprintf(buf, "%u\n", status_errors); } static DEVICE_ATTR_RO(status_errors); static ssize_t connected_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); bool connected; siox_master_lock(sdev->smaster); connected = sdev->connected; siox_master_unlock(sdev->smaster); return sprintf(buf, "%u\n", connected); } static DEVICE_ATTR_RO(connected); static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); u8 status; siox_master_lock(sdev->smaster); status = sdev->status_read_clean; siox_master_unlock(sdev->smaster); return sprintf(buf, "%d\n", status & SIOX_STATUS_WDG); } static DEVICE_ATTR_RO(watchdog); static ssize_t watchdog_errors_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_device *sdev = to_siox_device(dev); unsigned int watchdog_errors; siox_master_lock(sdev->smaster); watchdog_errors = sdev->watchdog_errors; siox_master_unlock(sdev->smaster); return sprintf(buf, "%u\n", watchdog_errors); } static DEVICE_ATTR_RO(watchdog_errors); static struct attribute *siox_device_attrs[] = { &dev_attr_type.attr, &dev_attr_inbytes.attr, &dev_attr_outbytes.attr, &dev_attr_status_errors.attr, &dev_attr_connected.attr, &dev_attr_watchdog.attr, &dev_attr_watchdog_errors.attr, NULL }; ATTRIBUTE_GROUPS(siox_device); static void siox_device_release(struct device *dev) { struct siox_device *sdevice = to_siox_device(dev); kfree(sdevice); } static struct device_type siox_device_type = { .groups = siox_device_groups, .release = siox_device_release, }; static int siox_match(struct device *dev, struct device_driver *drv) { if (dev->type != &siox_device_type) return 0; /* up to now there is only a single driver so keeping this simple */ return 1; } static struct bus_type siox_bus_type = { .name = "siox", .match = siox_match, }; static int siox_driver_probe(struct device *dev) { struct siox_driver *sdriver = to_siox_driver(dev->driver); struct siox_device *sdevice = to_siox_device(dev); int ret; ret = sdriver->probe(sdevice); return ret; } static int siox_driver_remove(struct device *dev) { struct siox_driver *sdriver = container_of(dev->driver, struct siox_driver, driver); struct siox_device *sdevice = to_siox_device(dev); int ret; ret = sdriver->remove(sdevice); return ret; } static void siox_driver_shutdown(struct device *dev) { struct siox_driver *sdriver = container_of(dev->driver, struct siox_driver, driver); struct siox_device *sdevice = to_siox_device(dev); sdriver->shutdown(sdevice); } static ssize_t active_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_master *smaster = to_siox_master(dev); return sprintf(buf, "%d\n", smaster->active); } static ssize_t active_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct siox_master *smaster = to_siox_master(dev); int ret; int active; ret = kstrtoint(buf, 0, &active); if (ret < 0) return ret; if (active) ret = siox_start(smaster); else ret = siox_stop(smaster); if (ret < 0) return ret; return count; } static DEVICE_ATTR_RW(active); static struct siox_device *siox_device_add(struct siox_master *smaster, const char *type, size_t inbytes, size_t outbytes, u8 statustype); static ssize_t device_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct siox_master *smaster = to_siox_master(dev); int ret; char type[20] = ""; size_t inbytes = 0, outbytes = 0; u8 statustype = 0; ret = sscanf(buf, "%19s %zu %zu %hhu", type, &inbytes, &outbytes, &statustype); if (ret != 3 && ret != 4) return -EINVAL; if (strcmp(type, "siox-12x8") || inbytes != 2 || outbytes != 4) return -EINVAL; siox_device_add(smaster, "siox-12x8", inbytes, outbytes, statustype); return count; } static DEVICE_ATTR_WO(device_add); static void siox_device_remove(struct siox_master *smaster); static ssize_t device_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct siox_master *smaster = to_siox_master(dev); /* XXX? require to write <type> <inbytes> <outbytes> */ siox_device_remove(smaster); return count; } static DEVICE_ATTR_WO(device_remove); static ssize_t poll_interval_ns_show(struct device *dev, struct device_attribute *attr, char *buf) { struct siox_master *smaster = to_siox_master(dev); return sprintf(buf, "%lld\n", jiffies_to_nsecs(smaster->poll_interval)); } static ssize_t poll_interval_ns_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct siox_master *smaster = to_siox_master(dev); int ret; u64 val; ret = kstrtou64(buf, 0, &val); if (ret < 0) return ret; siox_master_lock(smaster); smaster->poll_interval = nsecs_to_jiffies(val); siox_master_unlock(smaster); return count; } static DEVICE_ATTR_RW(poll_interval_ns); static struct attribute *siox_master_attrs[] = { &dev_attr_active.attr, &dev_attr_device_add.attr, &dev_attr_device_remove.attr, &dev_attr_poll_interval_ns.attr, NULL }; ATTRIBUTE_GROUPS(siox_master); static void siox_master_release(struct device *dev) { struct siox_master *smaster = to_siox_master(dev); kfree(smaster); } static struct device_type siox_master_type = { .groups = siox_master_groups, .release = siox_master_release, }; struct siox_master *siox_master_alloc(struct device *dev, size_t size) { struct siox_master *smaster; if (!dev) return NULL; smaster = kzalloc(sizeof(*smaster) + size, GFP_KERNEL); if (!smaster) return NULL; device_initialize(&smaster->dev); smaster->busno = -1; smaster->dev.bus = &siox_bus_type; smaster->dev.type = &siox_master_type; smaster->dev.parent = dev; smaster->poll_interval = DIV_ROUND_UP(HZ, 40); dev_set_drvdata(&smaster->dev, &smaster[1]); return smaster; } EXPORT_SYMBOL_GPL(siox_master_alloc); int siox_master_register(struct siox_master *smaster) { int ret; if (!siox_is_registered) return -EPROBE_DEFER; if (!smaster->pushpull) return -EINVAL; dev_set_name(&smaster->dev, "siox-%d", smaster->busno); mutex_init(&smaster->lock); INIT_LIST_HEAD(&smaster->devices); smaster->last_poll = jiffies; smaster->poll_thread = kthread_run(siox_poll_thread, smaster, "siox-%d", smaster->busno); if (IS_ERR(smaster->poll_thread)) { smaster->active = 0; return PTR_ERR(smaster->poll_thread); } ret = device_add(&smaster->dev); if (ret) kthread_stop(smaster->poll_thread); return ret; } EXPORT_SYMBOL_GPL(siox_master_register); void siox_master_unregister(struct siox_master *smaster) { /* remove device */ device_del(&smaster->dev); siox_master_lock(smaster); __siox_stop(smaster); while (smaster->num_devices) { struct siox_device *sdevice; sdevice = container_of(smaster->devices.prev, struct siox_device, node); list_del(&sdevice->node); smaster->num_devices--; siox_master_unlock(smaster); device_unregister(&sdevice->dev); siox_master_lock(smaster); } siox_master_unlock(smaster); put_device(&smaster->dev); } EXPORT_SYMBOL_GPL(siox_master_unregister); static struct siox_device *siox_device_add(struct siox_master *smaster, const char *type, size_t inbytes, size_t outbytes, u8 statustype) { struct siox_device *sdevice; int ret; size_t buf_len; sdevice = kzalloc(sizeof(*sdevice), GFP_KERNEL); if (!sdevice) return ERR_PTR(-ENOMEM); sdevice->type = type; sdevice->inbytes = inbytes; sdevice->outbytes = outbytes; sdevice->statustype = statustype; sdevice->smaster = smaster; sdevice->dev.parent = &smaster->dev; sdevice->dev.bus = &siox_bus_type; sdevice->dev.type = &siox_device_type; siox_master_lock(smaster); dev_set_name(&sdevice->dev, "siox-%d-%d", smaster->busno, smaster->num_devices); buf_len = smaster->setbuf_len + inbytes + smaster->getbuf_len + outbytes; if (smaster->buf_len < buf_len) { u8 *buf = krealloc(smaster->buf, buf_len, GFP_KERNEL); if (!buf) { dev_err(&smaster->dev, "failed to realloc buffer to %zu\n", buf_len); ret = -ENOMEM; goto err_buf_alloc; } smaster->buf_len = buf_len; smaster->buf = buf; } ret = device_register(&sdevice->dev); if (ret) { dev_err(&smaster->dev, "failed to register device: %d\n", ret); goto err_device_register; } smaster->num_devices++; list_add_tail(&sdevice->node, &smaster->devices); smaster->setbuf_len += sdevice->inbytes; smaster->getbuf_len += sdevice->outbytes; sdevice->status_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, "status_errors"); sdevice->watchdog_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, "watchdog"); sdevice->watchdog_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, "watchdog_errors"); sdevice->connected_kn = sysfs_get_dirent(sdevice->dev.kobj.sd, "connected"); siox_master_unlock(smaster); return sdevice; err_device_register: /* don't care to make the buffer smaller again */ err_buf_alloc: siox_master_unlock(smaster); kfree(sdevice); return ERR_PTR(ret); } static void siox_device_remove(struct siox_master *smaster) { struct siox_device *sdevice; siox_master_lock(smaster); if (!smaster->num_devices) { siox_master_unlock(smaster); return; } sdevice = container_of(smaster->devices.prev, struct siox_device, node); list_del(&sdevice->node); smaster->num_devices--; smaster->setbuf_len -= sdevice->inbytes; smaster->getbuf_len -= sdevice->outbytes; if (!smaster->num_devices) __siox_stop(smaster); siox_master_unlock(smaster); /* * This must be done without holding the master lock because we're * called from device_remove_store which also holds a sysfs mutex. * device_unregister tries to aquire the same lock. */ device_unregister(&sdevice->dev); } int __siox_driver_register(struct siox_driver *sdriver, struct module *owner) { int ret; if (unlikely(!siox_is_registered)) return -EPROBE_DEFER; if (!sdriver->set_data && !sdriver->get_data) { pr_err("Driver %s doesn't provide needed callbacks\n", sdriver->driver.name); return -EINVAL; } sdriver->driver.owner = owner; sdriver->driver.bus = &siox_bus_type; if (sdriver->probe) sdriver->driver.probe = siox_driver_probe; if (sdriver->remove) sdriver->driver.remove = siox_driver_remove; if (sdriver->shutdown) sdriver->driver.shutdown = siox_driver_shutdown; ret = driver_register(&sdriver->driver); if (ret) pr_err("Failed to register siox driver %s (%d)\n", sdriver->driver.name, ret); return ret; } EXPORT_SYMBOL_GPL(__siox_driver_register); static int __init siox_init(void) { int ret; ret = bus_register(&siox_bus_type); if (ret) { pr_err("Registration of SIOX bus type failed: %d\n", ret); return ret; } siox_is_registered = true; return 0; } subsys_initcall(siox_init); static void __exit siox_exit(void) { bus_unregister(&siox_bus_type); } module_exit(siox_exit); MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); MODULE_DESCRIPTION("Eckelmann SIOX driver core"); MODULE_LICENSE("GPL v2");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1