1/*
2 * Copyright 2010, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Philippe Houdoin, <phoudoin %at% haiku-os %dot% org>
7 */
8
9
10#include <net_buffer.h>
11#include <net_device.h>
12#include <net_stack.h>
13
14#include <KernelExport.h>
15
16#include <errno.h>
17#include <net/if.h>
18#include <net/if_dl.h>
19#include <net/if_media.h>
20#include <net/if_types.h>
21#include <new>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25#include <termios.h>
26#include <sys/uio.h>
27
28
29#define HDLC_FLAG_SEQUENCE	0x7e
30#define HDLC_CONTROL_ESCAPE	0x7d
31
32#define HDLC_ALL_STATIONS	0xff
33#define HDLC_UI				0x03
34
35#define HDLC_HEADER_LENGTH	4
36
37
38enum dialup_state {
39	DOWN,
40	DIALING,
41	UP,
42	HANGINGUP
43};
44
45struct dialup_device : net_device {
46	int				fd;
47	struct termios	line_config;
48	dialup_state 	state;
49	bigtime_t		last_closing_flag_sequence_time;
50	bool			data_mode;
51	char			init_string[64];
52	char			dial_string[64];
53	char			escape_string[8];
54	bigtime_t		escape_silence;
55	char			hangup_string[16];
56	bigtime_t		tx_flag_timeout;
57	uint32			rx_accm;
58	uint32			tx_accm[8];
59};
60
61net_buffer_module_info* gBufferModule;
62static net_stack_module_info* sStackModule;
63
64
65//	#pragma mark -
66
67
68static status_t
69switch_to_command_mode(dialup_device* device)
70{
71	if (device->state != UP)
72		return B_ERROR;
73
74	if (!device->data_mode)
75		return B_OK;
76
77	snooze(device->escape_silence);
78
79	ssize_t size = write(device->fd, device->escape_string,
80			strlen(device->escape_string));
81	if (size != (ssize_t)strlen(device->escape_string))
82		return B_IO_ERROR;
83
84	snooze(device->escape_silence);
85	device->data_mode = false;
86	return B_OK;
87}
88
89
90#if 0
91static status_t
92switch_to_data_mode(dialup_device* device)
93{
94	if (device->state != UP)
95		return B_ERROR;
96
97	if (device->data_mode)
98		return B_OK;
99
100	// TODO: check if it's needed, as these days any
101	// escaped AT commands switch back to data mode automatically
102	// after their completion...
103	ssize_t size = write(device->fd, "ATO", 3);
104	if (size != 3)
105		return B_IO_ERROR;
106
107	device->data_mode = true;
108	return B_OK;
109}
110#endif
111
112
113static status_t
114send_command(dialup_device* device, const char* command)
115{
116	status_t status;
117	if (device->data_mode) {
118		status = switch_to_command_mode(device);
119		if (status != B_OK)
120			return status;
121	}
122
123	ssize_t bytesWritten = write(device->fd, command, strlen(command));
124	if (bytesWritten != (ssize_t)strlen(command))
125		return B_IO_ERROR;
126
127	if (write(device->fd, "\r", 1) != 1)
128		return B_IO_ERROR;
129
130	return B_OK;
131}
132
133
134static status_t
135read_command_reply(dialup_device* device, const char* command,
136	char* reply, int replyMaxSize)
137{
138	if (device->data_mode)
139		return B_ERROR;
140
141	int i = 0;
142	while (i < replyMaxSize) {
143
144		ssize_t bytesRead = read(device->fd, &reply[i], 1);
145		if (bytesRead != 1)
146			return B_IO_ERROR;
147
148		if (reply[i] == '\n') {
149			// filter linefeed char
150			continue;
151		}
152
153		if (reply[i] == '\r') {
154			reply[i] = '\0';
155
156			// is command reply or command echo (if any) ?
157			if (!strcasecmp(reply, command))
158				return B_OK;
159
160			// It's command echo line. Just ignore it.
161			i = 0;
162			continue;
163		}
164		i++;
165	}
166
167	// replyMaxSize not large enough to store the full reply line.
168	return B_NO_MEMORY;
169}
170
171
172static status_t
173hangup(dialup_device* device)
174{
175	if (device->state != UP)
176		return B_ERROR;
177
178	// TODO: turn device's DTR down instead. Or do that too after sending command
179	char reply[8];
180
181	if (send_command(device, device->hangup_string) != B_OK
182		|| read_command_reply(device, device->hangup_string,
183			reply, sizeof(reply)) != B_OK
184		|| strcmp(reply, "OK"))
185		return B_ERROR;
186
187	device->state = DOWN;
188	return B_OK;
189}
190
191
192//	#pragma mark -
193
194
195status_t
196dialup_init(const char* name, net_device** _device)
197{
198	// make sure this is a device in /dev/ports
199	if (strncmp(name, "/dev/ports/", 11))
200		return B_BAD_VALUE;
201
202	status_t status = get_module(NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule);
203	if (status < B_OK)
204		return status;
205
206	dialup_device* device = new (std::nothrow)dialup_device;
207	if (device == NULL) {
208		put_module(NET_BUFFER_MODULE_NAME);
209		return B_NO_MEMORY;
210	}
211
212	memset(device, 0, sizeof(dialup_device));
213
214	strcpy(device->name, name);
215	device->flags = IFF_POINTOPOINT;
216	device->type = IFT_PPP; // this device handle RFC 1331 frame format only
217	device->mtu = 1500;
218	device->media = 0;
219	device->header_length = HDLC_HEADER_LENGTH;
220
221	device->fd = -1;
222	device->state = DOWN;
223	device->data_mode = false;
224	device->last_closing_flag_sequence_time = 0;
225
226	// default AT strings
227	strncpy(device->init_string, "ATZ", sizeof(device->init_string));
228	strncpy(device->dial_string, "ATDT", sizeof(device->dial_string));
229	strncpy(device->hangup_string, "ATH0", sizeof(device->hangup_string));
230
231	strncpy(device->escape_string, "+++", sizeof(device->escape_string));
232	device->escape_silence = 1000000;
233
234	device->tx_flag_timeout = 1000000;
235
236	// default rx & tx Async-Control-Character-Map
237	memset(&device->rx_accm, 0xFF, sizeof(device->rx_accm));
238	memset(&device->tx_accm, 0xFF, sizeof(device->tx_accm));
239
240	*_device = device;
241	return B_OK;
242}
243
244
245status_t
246dialup_uninit(net_device* _device)
247{
248	dialup_device* device = (dialup_device*)_device;
249	delete device;
250
251	put_module(NET_BUFFER_MODULE_NAME);
252	gBufferModule = NULL;
253	return B_OK;
254}
255
256
257status_t
258dialup_up(net_device* _device)
259{
260	dialup_device* device = (dialup_device*)_device;
261
262	device->fd = open(device->name, O_RDWR);
263	if (device->fd < 0)
264		return errno;
265
266	device->media = IFM_ACTIVE;
267
268	// init port
269	if (ioctl(device->fd, TCGETA, &device->line_config,
270		sizeof(device->line_config)) < 0)
271		goto err;
272
273	// adjust options
274	device->line_config.c_cflag &= ~CBAUD;
275	device->line_config.c_cflag &= CSIZE;
276	device->line_config.c_cflag &= CS8;
277	device->line_config.c_cflag |= B115200;	// TODO: make this configurable too...
278	device->line_config.c_cflag |= (CLOCAL | CREAD);
279	device->line_config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
280	device->line_config.c_oflag &= ~OPOST;
281	device->line_config.c_cc[VMIN] = 0;
282	device->line_config.c_cc[VTIME] = 10;
283
284	// set new options
285	if(ioctl(device->fd, TCSETA, &device->line_config,
286		sizeof(device->line_config)) < 0)
287		goto err;
288
289	// init modem & start dialing phase
290
291	char reply[32];
292
293	if (strlen(device->init_string) > 0) {
294		// Send modem init string
295		if (send_command(device, device->init_string) != B_OK
296			|| read_command_reply(device, device->init_string,
297				reply, sizeof(reply)) != B_OK
298			|| strcmp(reply, "OK")) {
299			errno = B_IO_ERROR;
300			goto err;
301		}
302	}
303
304	reply[0] = '\0';
305
306	if (strlen(device->dial_string) > 0) {
307		// Send dialing string
308		device->state = DIALING;
309		if (send_command(device, device->dial_string) != B_OK
310			|| read_command_reply(device, device->dial_string,
311				reply, sizeof(reply)) != B_OK
312			|| strncmp(reply, "CONNECT", 7)) {
313			errno = B_IO_ERROR;
314			goto err;
315		}
316	}
317
318	device->state = UP;
319	device->data_mode = true;
320
321	device->media |= IFM_FULL_DUPLEX;
322	device->flags |= IFF_LINK;
323
324	device->link_quality = 1000;
325	if (strlen(reply) > 7) {
326		// get speed from "CONNECTxxxx" reply
327		device->link_speed = atoi(&reply[8]);
328	} else {
329		// Set default speed (theorically, it could be 300 bits/s even)
330		device->link_speed = 19200;
331	}
332
333	return B_OK;
334
335err:
336	close(device->fd);
337	device->fd = -1;
338	device->media = 0;
339
340	return errno;
341}
342
343
344void
345dialup_down(net_device* _device)
346{
347	dialup_device* device = (dialup_device*)_device;
348
349	if (device->flags & IFF_LINK
350		&& hangup(device) == B_OK)
351		device->flags &= ~IFF_LINK;
352
353	close(device->fd);
354	device->fd = -1;
355	device->media = 0;
356}
357
358
359status_t
360dialup_control(net_device* _device, int32 op, void* argument,
361	size_t length)
362{
363	dialup_device* device = (dialup_device*)_device;
364	return ioctl(device->fd, op, argument, length);
365}
366
367
368status_t
369dialup_send_data(net_device* _device, net_buffer* buffer)
370{
371	dialup_device* device = (dialup_device*)_device;
372
373	if (device->fd == -1)
374		return B_FILE_ERROR;
375
376	dprintf("try to send HDLC packet of %" B_PRIu32 " bytes "
377		"(flags 0x%" B_PRIx32 "):\n", buffer->size, buffer->flags);
378
379	if (buffer->size < HDLC_HEADER_LENGTH)
380		return B_BAD_VALUE;
381
382	iovec* ioVectors = NULL;
383	iovec* ioVector;
384	uint8* packet = NULL;
385	int packetSize = 0;
386	status_t status;
387	ssize_t bytesWritten;
388
389	uint32 vectorCount = gBufferModule->count_iovecs(buffer);
390	if (vectorCount < 1) {
391		status = B_BAD_VALUE;
392		goto err;
393	}
394
395	ioVectors = (iovec*)malloc(sizeof(iovec)*vectorCount);
396	if (ioVectors == NULL) {
397		status = B_NO_MEMORY;
398		goto err;
399	}
400	gBufferModule->get_iovecs(buffer, ioVectors, vectorCount);
401
402	// encode HDLC packet
403
404	// worst case: begin and end sequence flags plus each payload byte escaped
405	packet = (uint8*)malloc(2 + 2 * buffer->size);
406	if (packet == NULL) {
407		status = B_NO_MEMORY;
408		goto err;
409	}
410
411	// Mark frame start if the prior frame closing flag was sent
412	// more than a second ago.
413	// Otherwise, the prior closing flag sequence is the open flag of this
414	// frame
415	if (device->tx_flag_timeout
416		&& system_time() - device->last_closing_flag_sequence_time
417			> device->tx_flag_timeout) {
418		packet[packetSize++] = HDLC_FLAG_SEQUENCE;
419	}
420
421	// encode frame data
422	ioVector = ioVectors;
423	while (vectorCount--) {
424		uint8* data = (uint8*) ioVector->iov_base;
425		for (unsigned int i = 0; i < ioVector->iov_len; i++) {
426			if (data[i] < 0x20
427				|| data[i] == HDLC_FLAG_SEQUENCE
428				|| data[i] == HDLC_CONTROL_ESCAPE) {
429				// needs escape
430				packet[packetSize++] = HDLC_CONTROL_ESCAPE;
431				packet[packetSize++] = data[i] ^ 0x20;
432			} else
433				packet[packetSize++] = data[i];
434		}
435		// next io vector
436		ioVector++;
437	}
438
439	// mark frame end
440	packet[packetSize++] = HDLC_FLAG_SEQUENCE;
441
442	// send HDLC packet
443
444	bytesWritten = write(device->fd, packet, packetSize);
445	if (bytesWritten < 0) {
446		status = errno;
447		goto err;
448	}
449	device->last_closing_flag_sequence_time = system_time();
450
451	status = B_OK;
452	goto done;
453
454err:
455done:
456	free(ioVectors);
457	free(packet);
458	return status;
459}
460
461
462status_t
463dialup_receive_data(net_device* _device, net_buffer** _buffer)
464{
465	dialup_device* device = (dialup_device*)_device;
466
467	if (device->fd == -1)
468		return B_FILE_ERROR;
469
470	net_buffer* buffer = gBufferModule->create(256);
471	if (buffer == NULL)
472		return ENOBUFS;
473
474	status_t status;
475	ssize_t bytesRead;
476	uint8* data = NULL;
477	uint8* packet = (uint8*)malloc(2 + 2 * buffer->size);
478	if (packet == NULL) {
479		status = B_NO_MEMORY;
480		goto err;
481	}
482
483	status = gBufferModule->append_size(buffer,
484		device->mtu + HDLC_HEADER_LENGTH, (void**)&data);
485	if (status == B_OK && data == NULL) {
486		dprintf("scattered I/O is not yet supported by dialup device.\n");
487		status = B_NOT_SUPPORTED;
488	}
489	if (status < B_OK)
490		goto err;
491
492	while (true) {
493		bytesRead = read(device->fd, data, device->mtu + HDLC_HEADER_LENGTH);
494		if (bytesRead < 0) {
495			// TODO
496		}
497	}
498
499	status = gBufferModule->trim(buffer, bytesRead);
500	if (status < B_OK) {
501		atomic_add((int32*)&device->stats.receive.dropped, 1);
502		goto err;
503	}
504
505	*_buffer = buffer;
506	status = B_OK;
507	goto done;
508
509err:
510	gBufferModule->free(buffer);
511
512done:
513	free(packet);
514	return status;
515}
516
517
518status_t
519dialup_set_mtu(net_device* _device, size_t mtu)
520{
521	dialup_device* device = (dialup_device*)_device;
522
523	device->mtu = mtu;
524	return B_OK;
525}
526
527
528status_t
529dialup_set_promiscuous(net_device* _device, bool promiscuous)
530{
531	return B_NOT_SUPPORTED;
532}
533
534
535status_t
536dialup_set_media(net_device* device, uint32 media)
537{
538	return B_NOT_SUPPORTED;
539}
540
541
542status_t
543dialup_add_multicast(struct net_device* _device, const sockaddr* _address)
544{
545	return B_NOT_SUPPORTED;
546}
547
548
549status_t
550dialup_remove_multicast(struct net_device* _device, const sockaddr* _address)
551{
552	return B_NOT_SUPPORTED;
553}
554
555
556static status_t
557dialup_std_ops(int32 op, ...)
558{
559	switch (op) {
560		case B_MODULE_INIT:
561		{
562			status_t status = get_module(NET_STACK_MODULE_NAME,
563				(module_info**)&sStackModule);
564			if (status < B_OK)
565				return status;
566
567			return B_OK;
568		}
569
570		case B_MODULE_UNINIT:
571		{
572			put_module(NET_STACK_MODULE_NAME);
573			return B_OK;
574		}
575
576		default:
577			return B_ERROR;
578	}
579}
580
581
582net_device_module_info sDialUpModule = {
583	{
584		"network/devices/dialup/v1",
585		0,
586		dialup_std_ops
587	},
588	dialup_init,
589	dialup_uninit,
590	dialup_up,
591	dialup_down,
592	dialup_control,
593	dialup_send_data,
594	dialup_receive_data,
595	dialup_set_mtu,
596	dialup_set_promiscuous,
597	dialup_set_media,
598	dialup_add_multicast,
599	dialup_remove_multicast,
600};
601
602module_info* modules[] = {
603	(module_info*)&sDialUpModule,
604	NULL
605};
606