...

Source file src/github.com/jacobsa/go-serial/serial/open_darwin.go

Documentation: github.com/jacobsa/go-serial/serial

     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  
    26  package serial
    27  
    28  import (
    29  	"errors"
    30  	"io"
    31  )
    32  import "os"
    33  import "syscall"
    34  import "unsafe"
    35  
    36  // termios types
    37  type cc_t byte
    38  type speed_t uint64
    39  type tcflag_t uint64
    40  
    41  // sys/termios.h
    42  const (
    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  
    63  const (
    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
    74  type 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.
    87  func 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  
   109  func 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  
   199  func 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  

View as plain text