// 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);} Lines 1–500 of 980. Load more
// 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); }