980 lines · 22.1 KB · C
RawDownload
// 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.