LINUX.ORG.RU

приём сигнала BREAK через последовательный порт


0

1

Хотелось бы, чтобы по приёму сигнала BREAK (лог. 0 в течении 11 и более битовых периодов) очищались буфера данных (это коллизия на шине — всё начинать сначала, а также программа принимала сигнал SIGINT, например.

Примерно это и обещает документация (man tcsetattr) если установлен BRKINT и сброшены IGNBRK в c_iflag. Но там же указывается — только если последовательный порт является «управляющим терминалом» для процесса. Последнее условие очень мешает.

Что приходит в голову. После открывания порта сохранить свой pid, сделать fork, сделать setsid, переоткрыть (это вообще поможет?) открытый порт без O_NOCTTY... по получении SIGINT делать kill(первый pid, SIGUSR1), например.

Как-то это очень ненормально... не?

Можно, конечно, удовлетвориться 0 вместо BREAK (~IGNBRK и ~BRKINT). Это если данные в ASCII, а если нет? Как быть тогда? 0xFF 0 0 тоже не вариант.

Если данные в ASCII, то 0 вместо BREAK вполне удовлетворяет для приёма. Но нужно прекращать передачу после BREAK. Без сигнала пока весь приёмный буфер считает до 0 — долго. С сигналом ok (быстрей для userspace никак уже), но там же в мане написано input and output queues to be flushed — для input это совершенно не нужно (потеряются данные идущие сразу после BREAK, оно ж не моментально всё делается, и потеряются неповреждённые данные до BREAK). Действительно flushed? Получается разумный компромисс, если ASCII, то ориентироваться только по 0 во входном потоке. А если двоичные данные, то извращаться таки как-то с сигналом. Да проще из /proc/tty/driver/serial о BREAK узнать... Что же это такое, какой неудобный интерфейс.


Короче, сделал как написал, порядочный говнокод, но суть видна. Но этож как-то через ж.

#include <assert.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>

volatile unsigned breaks=0;
unsigned child;
pid_t pid;

void sigusr(int signo)
{
	breaks++;
}

/* called in child */
void sigbreak(int signo)
{
	kill(pid /*parent*/, SIGUSR1);
}

void sigchild(int signo)
{
	exit(1);
}

void sigquit(int signo)
{
	kill(child, SIGQUIT);
	exit(1);
}

int main(int ac, char *av[])
{
	if (av[1]==NULL) puts("no file name specified."), exit(1);

	int fd = open(av[1], O_RDWR|O_NONBLOCK/*|O_NOCTTY*/);
	if (fd < 0) perror(av[1]), exit(1);
	//int i=fcntl(fd, F_GETFL);
	//i=fcntl(fd, F_SETFL, i | O_NONBLOCK);
	struct termios ts;
	tcgetattr(fd, &ts);
	cfmakeraw(&ts);
	cfsetspeed(&ts, B9600);
	ts.c_cflag |= CLOCAL | CREAD;
	ts.c_iflag&=~IGNBRK;
	ts.c_iflag|=BRKINT;
	//ts.c_iflag&=~BRKINT;
	ts.c_iflag|=PARMRK; /*|IGNPAR; // ?*/
	//ts.c_iflag&=~PARMRK;
	tcsetattr(fd, TCSANOW, &ts);

	pid=getpid();
	signal(SIGCHLD, sigchild);
	signal(SIGQUIT, sigquit);
	child=fork();
	if (child<0) perror("fork"), exit(1);
	if (!child) {
		/* in child */
		if (setsid() < 0) perror("setsid"), exit(1);
		//if (ioctl(fd, TIOCSCTTY, 1)<0) perror("TIOCSTTY"), exit(1);
		signal(SIGINT, sigbreak);
		while (1) sleep(86400);
	}
	else { /* parent */
		signal(SIGUSR1, sigusr);
		signal(SIGINT, sigquit);
	}

	unsigned b=0;
	while (1) {
		if (breaks!=b) b=breaks, printf("breaks=%u\n", b);
		unsigned char c;
		if (read(fd, &c, 1)>0) {
			if (isprint(c)) putchar(c);
			else printf(" %2.2X ", c);
		}
		else {
			usleep(100000);
			putchar('.');
		}
		fflush(stdout);
	}

	return 0;
}

fk0
() автор топика
Ответ на: комментарий от fk0

Вдогонку. По BREAK очередь не чистится, tcflush (из обработчика SIGINT) с любыми аргументами не имеет действия (возможно, это зависит от драйвера).

Выводы: получать BREAK через SIGINT имеет смысл только при приёме бинарных данных (и через дочерний процесс специально для того выделенный). Если ASCII, то проще довольстоваться нулём вместо BREAK. На tcflush() расчитывать нельзя и в конкретном случае стоит просто не пытаться записать в порт много, записывать по мере передачи прошлых пакетов, если в приёме не было нуля (т.е. BREAK или коллизии, и нечётности тоже).

fk0
() автор топика
Ответ на: комментарий от fk0

Ещё вдогонку. tcsendbreak перед write() даёт белиберду на выходе (т.е. BREAK посылается параллельно с данными и их портит). И если ещё стоп-бит один — весь пакет порченный (если два — только первые байты). Не известно даже, что лучше. Как вариант: делать TIOCSBRK, пауза, TIOCCBRK, потом write(). Но не факт, что поддерживается драйвером. Или TCSBRKP с более определённой (vs TCSBRK) задержкой, но опять же не факт...

fk0
() автор топика
Ответ на: комментарий от fk0

т.е. BREAK посылается параллельно с данными и их портит

ога..давным давно, в далекой далёкой галактике..чтобы реализовать x.28 во всей красе, приходилось отказываться от терминальной прослойки имени Алена Кокса и реализовывать свой драйвер UART, с собственными ioctl. Вспомнилось, что приходилось держать FIFO достаточной ёмкости для BREAK и что «не все йогурты одинаково полезны» - разные микрухи по разному его отрабатывают, некоторые чистят внутренний буфер, некоторые нет.

кстати, uart`ы настолько просты, что проще действительно сделать собственный драйвер, чем побороть все мамонтовы говна накопленные в unix/linux за годы

MKuznetsov ★★★★★
()

Вы сами себе противоречите

Хотелось бы, чтобы по приёму сигнала BREAK (лог. 0 в течении 11 и более битовых периодов) очищались буфера данных (это коллизия на шине — всё начинать сначала

для input это совершенно не нужно (потеряются данные идущие сразу после BREAK

Чего вы вообще хотите сделать ? В курсе что буферов может быть два - аппаратный FIFO и програмный в ядре, а может и один - только ядерный ? Насколько скоростной у вас UART ? Если 115200 то до приема следующего байта у вас ядро успеет пару десятков раз SIGINT бросить - какие в попу потерянные данные ?

anonymous
()
Ответ на: комментарий от anonymous

Не успеет... Система многозадачная, пока до моего процесса дойдёт планировщик на 115200 уже всё. Хотя у меня 9600, да это не важно.

Противоречия нет. Я хочу, чтоб терялось всё что до (по времени) BREAK, но сохранялось всё что немедленно после.

Таки я согласен, в Unix ещё те наслоения... В Windows не лучше впрочем.

Написано больше для других: BREAK нельзя использовать толком в протоколе, только для каких-то редких событий не требующих быстрой обработки.

fk0
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.