Как стать автором
Обновить

Обзор шины SPI и разработка драйвера ведомого SPI устройства для embedded Linux (Часть вторая, практическая)

Время на прочтение15 мин
Количество просмотров48K
Это вторая часть моей статьи по разработке драйверов для ведомых SPI устройств в Linux. Предыдущая часть находится здесь.

3. Разработка userspace протокольного SPI драйвера с использованием spidev


Как уже было сказано выше, для SPI устройств существует ограниченная поддержка userspace API, с поддержкой базовых полудуплексных read() и write() вызовов для доступа к ведомым SPI устройствам. Используя ioctl() вызовы, можно производить полнодуплексный обмен данными с ведомым устройством, а также изменение параметров устройства.

Есть ряд причин, когда может возникнуть желание использовать данный userspace API:
  • Прототипирование в окружении не подверженном возникновению неисправимых ошибок. Невалидные указатели в пространстве пользователя обычно не могут привести к краху всей системы.
  • Разработка простых протоколов, используемых для обмена данными с микроконтроллерами, работающими в режиме ведомого SPI устройства, которые необходимо часто менять.

Конечно же, существуют драйверы, которые невозможно реализовать средствами userspace API, так как им необходим доступ к другим интерфейсам ядра (например, обработчики прерываний или другие подсистемы стека драйверов), недоступным в пространстве пользователя.

Для включения поддержки spidev необходимо:
1. При настройке ядра в menuconfig активировать пункт:
Device Drivers
	    SPI support
	        User mode SPI device driver support

2. Добавить в массив структур spi_board_info, о котором шла речь в предыдущем пункте, в файле платы:
{	/* spidev */
	.modalias	= "spidev",
	.chip_select	= 2,
	.max_speed_hz	= 15 * 1000 * 1000,
	.mode 		= SPI_MODE_0,
	.bus_num	= 1,
},


После пересборки и загрузки нового ядра в системе появится соответствующее устройство с именем вида /dev/spidevB.C, где B — номер шины SPI, а C — номер Chip select'а. Данное устройство нельзя создавать вручную через mknod, его должны автоматически создавать такие службы как udev/mdev.

Отлично, устройство у нас есть. Осталось научиться с ним работать. Предположим мы хотим отправить байт 0x8E в устройство висящее на SPI1 с номером CS 2. Наверное, самый простой способ это сделать выглядит так:
echo -ne "\x8e">/dev/spidev1.2

После чего на моём тестовом устройстве можно было наблюдать такую картину:
image


Следует сказать пару слов о тестовом устройстве. Наверное, самое главное в том, что оно почти что бесполезно и делалось лишь для изучения работы с SPI в Linux. Оно сделано на одном сдвиговом регистре 74HC164N, а на 3-х элементах 2И-НЕ из 74HC132N сделано некоторое подобие chipselect'а, которое разрешает синхронизирующий сигнал только при низком уровне на входе ~CS (сразу хочу заметить, да, я знаю о существовании 74HC595, но у меня в городе её достать не удалось). У данного устройства лишь одна функция — отображать на светодиодах последний записанный в неё байт. Так как моё устройство не является полностью «честным», при чтении из него мы будем получать не то что записали (как должно было быть), а значение сдвинутое на один бит влево.

Параметры работы с ведомым устройством можно настроить посредством ioctl() вызовов. Они позволяют изменить скорость передачи данных, размер передаваемого слова, порядок байт в передаче, и естественно режим работы SPI.
Следующие ioctl() запросы позволяют управлять параметрами ведомого устройства:
  • SPI_IOC_RD_MODE, SPI_IOC_WR_MODE — в случае чтения (RD) байту на который передан указатель, производится присваивание значения текущего SPI режима. В случае записи (WR), для устройства устанавливается режим соответственно значению байта по переданному указателю. Для задания режима используются константы SPI_MODE_0… SPI_MODE_3, либо же комбинация констант SPI_CPHA (фаза синхронизации, захват по переднему фронту, если установлено) и SPI_CPOL (полярность синхронизации, сигнал синхронизации начинается с высокого уровня) через побитовое «или».
  • SPI_IOC_RD_LSB_FIRST, SPI_IOC_WR_LSB_FIRST — передаёт указатель на байт, который определяет выравнивание битов при передаче SPI слов. Нулевое значение показывает что старший бит является первым (MSB-first), другие значения показывают что используется более редкий вариант, и младший бит является первым (LSB-first). В обоих случаях каждое слово будет выравнено по правому краю, так что неиспользуемые/неопределённые биты будут находится в старших разрядах. RD/WR — чтение/запись параметра, определяющего выравнивание битов в словах, соответственно.
  • SPI_IOC_RD_BITS_PER_WORD, SPI_IOC_WR_BITS_PER_WORD — передаёт указатель на байт, определяющий количество бит на слово при передаче данных по SPI. Нулевое значение соответствует восьми битам. RD/WR — чтение/запись количества бит на слово соответственно.
  • SPI_IOC_RD_MAX_SPEED_HZ, SPI_IOC_WR_MAX_SPEED_HZ — передаёт указатель на переменную u32, которая определяет максимальную скорость передачи данных по SPI в Гц. Как правило, контроллер не может точно установить заданную скорость.

Изменяя частоту, я узнал что моё тестовое устройство может работать на частоте не выше примерно 15 МГц, что не так уж плохо с учётом длины шлейфов около 25 см, сборке на монтажной плате и соединения контактов с помощью МГТФа.

Теперь хочу сделать ещё одно важное замечание, смена порядка следования бит опять таки поддерживается не всеми контроллерами. Для того чтобы узнать какие функции поддерживает контроллер надо смотреть битовую маску spi_master.mode_bits. Определить значение битов в маске можно из определения флагов в структуре spi_device. Я не стану здесь приводить полные описания структур spi_device и spi_master, так как они не являются критически важными для понимания в данном случае. Ссылку на документацию, в которой можно найти описания всех указанных структур, я дам в конце статьи.

Как я упомянул вначале, spidev позволяет производить полудуплексные передачи, с помощью соответсвтующей команды ioctl():
int ret;
ret = ioctl(fd, SPI_IOC_MESSAGE(num), tr);

где num — количество передач в массиве структур типа spi_ioc_transfer;
tr — указатель на массив структур spi_ioc_transfer;
В случае неудачи возвращается отрицательное значение, в случае успеха — суммарное количество успешно переданных байт для всех передач.
Сама структура передачи имеет следующий вид:
struct spi_ioc_transfer {
	__u64		tx_buf;
	__u64		rx_buf;
	__u32		len;
	__u32		speed_hz;
	__u16		delay_usecs;
	__u8		bits_per_word;
	__u8		cs_change;
	__u32		pad;
};

tx_buf и rx_buf — хранят указатели в пространстве пользователя на буфер для передачи/приёма данных соответственно. Если tx_buf равен NULL, то будут выталкиваться нули. В случае если rx_buf установлен в NULL, то данные полученные от ведомого игнорируются.
len — длина буферов приёма и передачи (rx и tx) в байтах.
speed_hz — переопределяет скорость передачи данных для данной передачи.
bits_per_word — переопределяет количество бит на слово для данной передачи.
delay_usecs — задержка в микросекундах перед деактивацией устройства (перед вызовом cs_deactivate), после передачи последнего бита данных.

Практически все поля структуры spi_ioc_transfer соответствуют полям структуры spi_transfer. Буферы данных предварительно копируются в/из пространства ядра с помощью функций copy_from_user()/copy_to_user() в недрах драйвера spidev.
Как я уже выше говорил, не все контроллеры поддерживают возможность смены скорости и размера слова для каждой передачи в отдельности, так что лучше ставьте там нули, если хотите получить переносимый код. Именно поэтому стандартный пример для работы с spidev в полнодуплексном режиме, идущий в комплекте с документацией ядра, работать без исправлений в инициализации структуры spi_ioc_transfer не станет на чипах семейства at91.

Замечания:
  • На данный момент нет возможности получить реальную скорость с которой происходит выталкивание/захват бит данных для данного устройства.
  • На данный момент нельзя поменять полярность chip select'а через spidev. Каждое ведомое устройство деактивируется, когда оно не находится в стадии активного использования, позволяя другим драйверам обмениваться данными с соответствующими им устройствами.
  • Существует ограничение на количество байт, передаваемых при каждом запросе ввода/вывода. Как правило, ограничение соответствует размеру одной страницы памяти. Это значение может быть изменено с помощью параметра модуля ядра.
  • Так как SPI не имеет низкоуровневого способа подтверждения доставки, то не существует способа узнать о наличии каких-либо ошибок при передаче, или, например, при выборе несуществующего устройства.

Теперь я приведу пример, это упрощённый вариант программы для работы с spidev идущей в комплекте с ядром. Хотя в примере это явно не показано, но никто не запрещает использовать системные вызовы read() и write() для полудуплексного обмена данными.
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

static void pabort(const char *s)
{
	perror(s);
	abort();
}

static uint8_t mode = SPI_MODE_0;
static uint8_t bits = 0;
static uint32_t speed = 500000;

int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;
	uint8_t tx[] = { 0x81, 0x18 };
	uint8_t rx[] = {0, 0 };

	if(argc!=2) {
		fprintf(stderr, "Usage: %s <spidev>\n", argv[0]);
		exit(1);
	}
	
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
		pabort("can't open device");

	/* spi mode */
	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
	if (ret == -1)
		pabort("can't set spi mode");

	ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
	if (ret == -1)
		pabort("can't get spi mode");

	/* bits per word */
	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't set bits per word");

	ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't get bits per word");

	/* max speed hz */
	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't set max speed hz");

	ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't get max speed hz");

	printf("spi mode: %d\n", mode);
	printf("bits per word: %d\n", bits);
	printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
	
	/* full-duplex transfer */
	struct spi_ioc_transfer tr = {
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)rx,
		.len = 2,
		.delay_usecs = 0,
		.speed_hz = 0,
		.bits_per_word = bits,
	};

	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
	if (ret < 1)
		pabort("can't send spi message");

	for (ret = 0; ret < 2; ret++) {
		printf("%.2X ", rx[ret]);
	}
	puts("");

	close(fd);

	return ret;
}

Думаю здесь всё достаточно очевидно, все запросы передаваемые к устройству через ioctl() мы уже разобрали. Осталось только привести Makefile для сборки:

all: spidev_test 
CC = /opt/arm-2010q1/bin/arm-none-linux-gnueabi-gcc 
INCLUDES = -I. 
CCFLAGS = -O2 -Wall 
clean: 
	rm -f spidev_test 
spidev_test: spidev_test.c 
	$(CC) $(INCLUDES) $(CCFLAGS) spidev_test.c -o spidev_test

Единственное, понадобится укзать свой путь до кросс-компилятора в переменной CC.

4. Разработка протокольного SPI драйвера уровня ядра


Разработка модуля ядра тема гораздо более объёмная, поэтому в данном случае пойдём другим путём: я приведу вначале пример кода, потом дам краткое описание его работы, объясню как его задействовать. Описывать все подробности я не стану, иначе никакой статьи не хватит, просто укажу наиболее важные моменты, в разделе статьи о документации можно найти ссылки на всю необходимую информацию. В данном примере я покажу как сделать доступными атрибуты устройства через sysfs. Как реализовать драйвер предоставляющий доступ к устройству посредством файла устройства уже обсуждалось ранее: раз, два.
Мой драйвер будет предоставлять пользователю возможность изменения двух атрибутов:
value — в него можно записать число, которое нужно отобразить в бинарном виде с помощью светодиодов;
mode — переключатель режимов, позволяет выставить один из трёх режимов работы. Поддерживаются следующие режимы: 0 — стандартный режим отображения числа в бинарном виде, 1 — режим прогресс-бара с отображением слева-направо, 2 — режим прогресс-бара с отображением справа-налево;
В режиме прогресс-бара устройство будет отображать неразрывную линию светодиодов, показывающую какой процент значение записанное в value составляет от 256. Например, если записать в mode 1, а в value 128, то засветится 4 светодиода из 8 слева.
Если в номере режима выставить третий бит, то будет использоваться полнодуплексный режим (функция fdx_transfer()), вместо асинхронных вызовов spi_write() и spi_read(). Номера полнодуплексных режимов будут соответственно 4,5,6. Режиму номер 3 соответствует 0.
Ну а теперь сам код:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/spi/spi.h>

#define SPI_LED_DRV_NAME	"spi_led"
#define DRIVER_VERSION		"1.0"

static unsigned char led_mode=0;
static unsigned char fduplex_mode=0;
unsigned char retval=0;
char *mtx, *mrx;

static unsigned char stbl_tmp;

enum led_mode_t {LED_MODE_DEF, LED_MODE_L2R, LED_MODE_R2L };

static inline unsigned char led_progress(unsigned long val) {
	unsigned char i, result=0x00;
	
	val++;
	val/=32;
	
	for(i = 0; i < val; i++) {
		if(led_mode==LED_MODE_R2L)
			result|=(0x01<<i);
		else
			result|=(0x80>>i);
	}
	return (unsigned char)result;
}

static int fdx_transfer(struct spi_device *spi, unsigned char *val) {
	int ret;
	struct spi_transfer t = {
		.tx_buf		= mtx,
		.rx_buf 	= mrx,
		.len		= 1,
	};
	struct spi_message	m;
	
	mtx[0]=*val;
	mrx[0]=0;	
	
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	if((ret=spi_sync(spi, &m))<0)
		return ret;
	retval=mrx[0];
	return ret;
}

static ssize_t spi_led_store_val(struct device *dev,
				 struct device_attribute *attr,
				 const char *buf, size_t count)
{
	struct spi_device *spi = to_spi_device(dev);
	unsigned char tmp;
	unsigned long val;

	if (strict_strtoul(buf, 10, &val) < 0)
		return -EINVAL;
	if (val > 255)
		return -EINVAL;
	switch(led_mode) {
		case LED_MODE_L2R:
		case LED_MODE_R2L:
			tmp = led_progress(val);
			break;
		default:
			tmp = (unsigned char)val;
	}
	stbl_tmp=tmp;
	if(fduplex_mode)
		fdx_transfer(spi, &tmp);
	else
		spi_write(spi, &tmp, sizeof(tmp));
	return count;
}

static ssize_t spi_led_show_val(struct device *dev,
				struct device_attribute *attr,
                char *buf)
{
	unsigned char val;
	struct spi_device *spi = to_spi_device(dev);
	if(!fduplex_mode)
		spi_read(spi, &val, sizeof(val));
	return scnprintf(buf, PAGE_SIZE, "%d\n", fduplex_mode ? retval : val);
}

static ssize_t spi_led_store_mode(struct device *dev,
				 struct device_attribute *attr,
				 const char *buf, size_t count)
{
	unsigned long tmp;
	if (strict_strtoul(buf, 10, &tmp) < 0)
		return -EINVAL;
	if(tmp>6)
		return -EINVAL;
	led_mode = (unsigned char)tmp&0x03;
	fduplex_mode = ((unsigned char)tmp&0x04)>>2;
	return count;
}

static ssize_t spi_led_show_mode(struct device *dev,
				struct device_attribute *attr,
                char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", led_mode);
}

static DEVICE_ATTR(value, S_IWUSR|S_IRUSR, spi_led_show_val, spi_led_store_val);
static DEVICE_ATTR(mode, S_IWUSR|S_IRUSR, spi_led_show_mode, spi_led_store_mode);

static struct attribute *spi_led_attributes[] = {
	&dev_attr_value.attr,
	&dev_attr_mode.attr,
	NULL
};

static const struct attribute_group spi_led_attr_group = {
	.attrs = spi_led_attributes,
};

static int __devinit spi_led_probe(struct spi_device *spi) {
	int ret;
	
	spi->bits_per_word = 8;
	spi->mode = SPI_MODE_0;
	spi->max_speed_hz = 500000;
	ret = spi_setup(spi);
	if(ret<0)
		return ret;
	return sysfs_create_group(&spi->dev.kobj, &spi_led_attr_group);
}

static int __devexit spi_led_remove(struct spi_device *spi) {
	sysfs_remove_group(&spi->dev.kobj, &spi_led_attr_group);
	return 0;
}

static struct spi_driver spi_led_driver = {
	.driver = {
		.name	= SPI_LED_DRV_NAME,
		.owner	= THIS_MODULE,
	},
	.probe	= spi_led_probe,
	.remove	= __devexit_p(spi_led_remove),
};

static int __init spi_led_init(void) {
	mtx=kzalloc(1, GFP_KERNEL);
	mrx=kzalloc(1, GFP_KERNEL);
	return spi_register_driver(&spi_led_driver);
}

static void __exit spi_led_exit(void) {
	kfree(mtx);
	kfree(mrx);
	spi_unregister_driver(&spi_led_driver);
}

MODULE_AUTHOR("Lampus");
MODULE_DESCRIPTION("spi_led 8-bit");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRIVER_VERSION);

module_init(spi_led_init);
module_exit(spi_led_exit);

Теперь, нужно добавить наше устройство в список SPI устройств в файле платы. Для моей SK-AT91SAM9260 нужно открыть файл arch/arm/mach-at91/board-sam9260ek.c и в массив структур spi_board_info добавить оную для нашего устройства (по аналогии с spidev):
{	/* LED SPI */
	.modalias	= "spi_led",
	.chip_select	= 1,
	.max_speed_hz	= 15 * 1000 * 1000,
	.mode 			= SPI_MODE_0,
	.bus_num		= 1,
},

Как видно из кода выше моё устройство работает на частоте 15 Мгц, висит на SPI1 с номером CS 1. Если этого не сделать, то при загрузке модуля не будет происходить связывания драйвера с устройством.
Для сборки модуля я использую следующий Makefile:

ifneq ($(KERNELRELEASE),) 
obj-m := spi_led.o 
else 
KDIR := /media/stuff/StarterKit/new_src/linux-2.6.39.1_st3 
all: 
	$(MAKE) -C $(KDIR) M=`pwd` modules 
endif 

Переменная KDIR должна указывать на ваш путь с исходными кодами ядра.
Сборка производится следующим образом:

ARCH=arm CROSS_COMPILE=/opt/arm-2010q1/bin/arm-none-linux-gnueabi- make

где переменная CROSS_COMPILE указывает ваш префикс кросс-компилятора.
Теперь пересобираем ядро, перекидываем наш модуль на плату и загружаем его:

insmod /path/to/spi_led.ko

После чего в системе появятся атрибутами устройства, и в ней мы увидим следующую картину:

ls /sys/module/spi_led/drivers/spi:spi_led/spi1.1
driver  modalias  mode  power  subsystem  uevent  value

Теперь вернёмся непосредственно к коду. Смотреть надо начинать с конца. Макросы MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, MODULE_VERSION определяют информацию которая будет доступна с помощью команды modinfo, соответственно имя автора, описание модуля, лицензия и версия. Наиболее важное значение имеет указание лицензия, ибо при использовании не GPL лицензии вы не сможете дёргать код из модулей, использующих лицензию GPL.

Макросы module_init() и module_exit() определяют функции инициализации и выгрузки модуля соответственно. В случае если модуль собран статически, функция указанная в макросе module_exit никогда не будет вызываться.

В структуре struct spi_driver spi_led_driver устанавливаются ссылки на функции привязки драйвера к устройству (probe), функции отключения устройства(remove), также имя драйвера владелец. Также там могут устанавливаться ссылки на функции перехода в энергосберегающий режи (suspend) и выхода из него (resume). Если драйвер поддерживает несколько различных устройств, одного класса, то их идентификаторы сохраняются в поле id_table.

Регистрация SPI драйвера в системе производится с помощью функции spi_register_driver(struct spi_driver * sdrv). Именно после регистрации может быть произведено связывание устройства и драйвера. Если всё пройдёт успешно, то следующей будет вызвана функция определнная в указателе probe. Убрать регистрацию драйвера из системы можно соответственно с помощью функции spi_unregister_driver (struct spi_driver * sdrv)

Функция spi_led_probe() выполняет установку параметров контроллера для работы с устройством в структуре spi_device, определённых ранее в spi_board_info. После переопределения необходимых полей в структуре spi_device вызывается функция настройки контроллера spi_setup().

Теперь обсудим атрибуты. Про работу с атрибутами устройства через sysfs можно почитать в файле Documentation/filesystems/sysfs.txt. Макрос DEVICE_ATTR предназначен для определения структуры device_attribute. Например, такое определение
static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

эквиваленто следующему:
static struct device_attribute dev_attr_foo = {
	.attr	= {
		.name = "foo",
		.mode = S_IWUSR | S_IRUGO,
		.show = show_foo,
		.store = store_foo,
	},
};

где show – указатель на функцию выполянемую при открытии файла атрибута;
store – указатель на функцию выполянемую при запси в файл атрибута;
mode – определяет права доступа к файлу атрибута;
name – имя файла атрибута.
Для примера посмотрите на строку
static DEVICE_ATTR(value, S_IWUSR|S_IRUSR, spi_led_show_val, spi_led_store_val);

Она определяет аттрибут устройства с названием value, разрешённый на чтение/запись пользователем, функция-обработчик запси атрибута — spi_led_store_val, функция-обработчик чтения атрибута — spi_led_show_val. Один из указателей на функции store/show может быть равен NULL, если запись/чтение данного атрибута не предусмотрены.
Посмотрим как выглядит работа с даным атрибутом:

cd /sys/module/spi_led/drivers/spi:spi_led/spi1.1
ls -l value
-rw------- 1 root root 4096 Jun 29 14:10 value
echo "32">value
cat value 
64

Помните я упомянал что моя железка при чтении сдвигает данные на один бит влево? Вот именно поэтому мы получили 64 вместо записанных 32-х. Что происходит при записи числа в файл атрибута: функция strict_strtoul() пытается преобразовать полученный строковый буфер в число, потом идёт защита от дурака – проверка того что число не превышает 255. Если всё же оно больше, то возвращаем ошибку. Для пользователя это будет выгялядеть так:

# echo "257">value 
bash: echo: write error: Invalid argument

Дальше идёт проверка текущего режима работы и в зависимости от него устанавливается переменная tmp. В случае режима прогресс-бара будет получено число с «неразрывными» единичными битами, иначе в SPI просто будет выведено байт заданный пользователем без изменений. В завимости от флага fduplex_mode выбирается способ передачи: полудуплексный или полнодуплексный. В первом случае используется функция spi_write(), во втором самописная fdx_transfer().

Теперь переходим полнодуплесной передаче данных. Как видите паямть под буферы (указатели mtx, mrx) для передачи у меня выделяются с помощью функции kzmalloc. Как я уже говорил, это вызвано необходимостью расположения буферов в области памяти доступной для DMA. Теперь смотрим на саму функцию fdx_transfer(). По сути она собирает сообщение spi_message, и передаёт его с помощью функции spi_sync() Байт полученный при отправке сообщения сохраняется в глобальную переменную retval, которая и будет всегда возвращаться функцией чтения атрибута value, в случае выставленного флага fduplex_mode.

Работа с атрибутом mode заключается лишь в парсинге переданного режима, разбора его на два глобальные переменных led_mode и fduplex_mode, определяющие режим отображения и дуплекса соответственно.

Наверняка многие из вас обратили внимание на то, что не понятно что будет происходить при попытке одновременно записать файл атрибута из нескольких приложений. Ничего хорошего не произойдёт точно, здесь мы имеем явный race condition. Я постарался излишне не усложнять код, но для тех кому интересно как поступать в таких случаях рекомендую почитать Unreliable Guide To Locking

Надеюсь с остальным проблем для понимания возникнуть не должно.

5. Документация


Здесь уже обсуждался вопрос поиска документации по ядру.
Исходные коды ядра поставляются с набором документации в каталоге Documentation/. Как правило, с неё и стоит начать.
Ядро работает с собственной системой документирования исходного кода kerneldoc. С помощью неё можно сгенерировать документацию по системам ядра и API с помощью следующих команд из папки исходных текстов ядра:

sudo apt-get install xmlto
make htmldocs

Документация для последней стабильной версии ядра всегда доступна по адресу: www.kernel.org/doc
Наверное, первое что оттуда стоит прочесть это Unreliable Guide To Hacking The Linux Kernel: www.kernel.org/doc/htmldocs/kernel-hacking.html
Изучение работы c SPI в Linux следует начинать с обзорных документов в каталоге Documentation/spi, в частности spi-summary и spidev. Также там есть замечательные примеры для работы через spidev: spidev_fdx.c и spidev_test.c; надеюсь вы не забыли что для некоторых контроллеров в них может понадобится внести небольшие исправления.
Про работу с атрибутами устройства через sysfs можно почитать в файле Documentation/filesystems/sysfs.txt.
Весь API используемый для работы с SPI в ядре описан тут: SPI Linux kernel API description www.kernel.org/doc/htmldocs/device-drivers/spi.html
Для облегчения поиска чего-либо в кодах ядра стоит использовать Linux Cross Reference: lxr.linux.no или lxr.free-electrons.com
Также очень рекомендую презентации от Free Electrons: free-electrons.com/docs Там собран просто замечательный набор презентаций по Embedded Linux, кратко и ясно доносящих суть дела.

На этом всё, всем спасибо за внимание, можете ругать.
Теги:
Хабы:
Всего голосов 46: ↑45 и ↓1+44
Комментарии9

Публикации

Истории

Ближайшие события

22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань