1// Copyright 2011 Aaron Jacobs. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This file contains OS-specific constants and types that work on OS X (tested
16// on version 10.6.8).
17//
18// Helpful documentation for some of these options:
19//
20//     http://www.unixwiz.net/techtips/termios-vmin-vtime.html
21//     http://www.taltech.com/support/entry/serial_intro
22//     http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_16.html
23//     http://permalink.gmane.org/gmane.linux.kernel/103713
24//
25
26package serial
27
28import (
29	"errors"
30	"io"
31)
32import "os"
33import "syscall"
34import "unsafe"
35
36// termios types
37type cc_t byte
38type speed_t uint64
39type tcflag_t uint64
40
41// sys/termios.h
42const (
43	kCS5        = 0x00000000
44	kCS6        = 0x00000100
45	kCS7        = 0x00000200
46	kCS8        = 0x00000300
47	kCLOCAL     = 0x00008000
48	kCREAD      = 0x00000800
49	kCSTOPB     = 0x00000400
50	kIGNPAR     = 0x00000004
51	kPARENB     = 0x00001000
52	kPARODD     = 0x00002000
53	kCCTS_OFLOW = 0x00010000
54	kCRTS_IFLOW = 0x00020000
55	kCRTSCTS    = kCCTS_OFLOW | kCRTS_IFLOW
56
57	kNCCS = 20
58
59	kVMIN  = tcflag_t(16)
60	kVTIME = tcflag_t(17)
61)
62
63const (
64
65	// sys/ttycom.h
66	kTIOCGETA = 1078490131
67	kTIOCSETA = 2152231956
68
69	// IOKit: serial/ioss.h
70	kIOSSIOSPEED = 0x80045402
71)
72
73// sys/termios.h
74type termios struct {
75	c_iflag  tcflag_t
76	c_oflag  tcflag_t
77	c_cflag  tcflag_t
78	c_lflag  tcflag_t
79	c_cc     [kNCCS]cc_t
80	c_ispeed speed_t
81	c_ospeed speed_t
82}
83
84// setTermios updates the termios struct associated with a serial port file
85// descriptor. This sets appropriate options for how the OS interacts with the
86// port.
87func setTermios(fd uintptr, src *termios) error {
88	// Make the ioctl syscall that sets the termios struct.
89	r1, _, errno :=
90		syscall.Syscall(
91			syscall.SYS_IOCTL,
92			fd,
93			uintptr(kTIOCSETA),
94			uintptr(unsafe.Pointer(src)))
95
96	// Did the syscall return an error?
97	if errno != 0 {
98		return os.NewSyscallError("SYS_IOCTL", errno)
99	}
100
101	// Just in case, check the return value as well.
102	if r1 != 0 {
103		return errors.New("Unknown error from SYS_IOCTL.")
104	}
105
106	return nil
107}
108
109func convertOptions(options OpenOptions) (*termios, error) {
110	var result termios
111
112	// Ignore modem status lines. We don't want to receive SIGHUP when the serial
113	// port is disconnected, for example.
114	result.c_cflag |= kCLOCAL
115
116	// Enable receiving data.
117	//
118	// NOTE(jacobsa): I don't know exactly what this flag is for. The man page
119	// seems to imply that it shouldn't really exist.
120	result.c_cflag |= kCREAD
121
122	// Sanity check inter-character timeout and minimum read size options.
123	vtime := uint(round(float64(options.InterCharacterTimeout)/100.0) * 100)
124	vmin := options.MinimumReadSize
125
126	if vmin == 0 && vtime < 100 {
127		return nil, errors.New("Invalid values for InterCharacterTimeout and MinimumReadSize.")
128	}
129
130	if vtime > 25500 {
131		return nil, errors.New("Invalid value for InterCharacterTimeout.")
132	}
133
134	// Set VMIN and VTIME. Make sure to convert to tenths of seconds for VTIME.
135	result.c_cc[kVTIME] = cc_t(vtime / 100)
136	result.c_cc[kVMIN] = cc_t(vmin)
137
138	if !IsStandardBaudRate(options.BaudRate) {
139		// Non-standard baud-rates cannot be set via the standard IOCTL.
140		//
141		// Set an arbitrary baudrate. We'll set the real one later.
142		result.c_ispeed = 14400
143		result.c_ospeed = 14400
144	} else {
145		result.c_ispeed = speed_t(options.BaudRate)
146		result.c_ospeed = speed_t(options.BaudRate)
147	}
148
149	// Data bits
150	switch options.DataBits {
151	case 5:
152		result.c_cflag |= kCS5
153	case 6:
154		result.c_cflag |= kCS6
155	case 7:
156		result.c_cflag |= kCS7
157	case 8:
158		result.c_cflag |= kCS8
159	default:
160		return nil, errors.New("Invalid setting for DataBits.")
161	}
162
163	// Stop bits
164	switch options.StopBits {
165	case 1:
166		// Nothing to do; CSTOPB is already cleared.
167	case 2:
168		result.c_cflag |= kCSTOPB
169	default:
170		return nil, errors.New("Invalid setting for StopBits.")
171	}
172
173	// Parity mode
174	switch options.ParityMode {
175	case PARITY_NONE:
176		// Nothing to do; PARENB is already not set.
177	case PARITY_ODD:
178		// Enable parity generation and receiving at the hardware level using
179		// PARENB, but continue to deliver all bytes to the user no matter what (by
180		// not setting INPCK). Also turn on odd parity mode.
181		result.c_cflag |= kPARENB
182		result.c_cflag |= kPARODD
183	case PARITY_EVEN:
184		// Enable parity generation and receiving at the hardware level using
185		// PARENB, but continue to deliver all bytes to the user no matter what (by
186		// not setting INPCK). Leave out PARODD to use even mode.
187		result.c_cflag |= kPARENB
188	default:
189		return nil, errors.New("Invalid setting for ParityMode.")
190	}
191
192	if options.RTSCTSFlowControl {
193		result.c_cflag |= kCRTSCTS
194	}
195
196	return &result, nil
197}
198
199func openInternal(options OpenOptions) (io.ReadWriteCloser, error) {
200	// Open the serial port in non-blocking mode, since otherwise the OS will
201	// wait for the CARRIER line to be asserted.
202	file, err :=
203		os.OpenFile(
204			options.PortName,
205			syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK,
206			0600)
207
208	if err != nil {
209		return nil, err
210	}
211
212	// We want to do blocking I/O, so clear the non-blocking flag set above.
213	r1, _, errno :=
214		syscall.Syscall(
215			syscall.SYS_FCNTL,
216			uintptr(file.Fd()),
217			uintptr(syscall.F_SETFL),
218			uintptr(0))
219
220	if errno != 0 {
221		return nil, os.NewSyscallError("SYS_FCNTL", errno)
222	}
223
224	if r1 != 0 {
225		return nil, errors.New("Unknown error from SYS_FCNTL.")
226	}
227
228	// Set standard termios options.
229	terminalOptions, err := convertOptions(options)
230	if err != nil {
231		return nil, err
232	}
233
234	err = setTermios(file.Fd(), terminalOptions)
235	if err != nil {
236		return nil, err
237	}
238
239	if !IsStandardBaudRate(options.BaudRate) {
240		// Set baud rate with the IOSSIOSPEED ioctl, to support non-standard speeds.
241		r2, _, errno2 := syscall.Syscall(
242			syscall.SYS_IOCTL,
243			uintptr(file.Fd()),
244			uintptr(kIOSSIOSPEED),
245			uintptr(unsafe.Pointer(&options.BaudRate)))
246
247		if errno2 != 0 {
248			return nil, os.NewSyscallError("SYS_IOCTL", errno2)
249		}
250
251		if r2 != 0 {
252			return nil, errors.New("Unknown error from SYS_IOCTL.")
253		}
254	}
255
256	// We're done.
257	return file, nil
258}
259