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