blob: 41577654264f362d186b2709f2a3dc040ceb30a9 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package serial provides support for serial connections.
package serial
import (
"context"
"fmt"
"io"
"net"
"strings"
"time"
"go.fuchsia.dev/fuchsia/tools/lib/iomisc"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
const (
defaultBaudRate = 115200
// Printed to the serial console when ready to accept user input.
consoleCursor = "\n$"
)
var diagnosticCmds = []Command{
{[]string{"k", "threadload"}, 200 * time.Millisecond}, // Turn on threadload
{[]string{"k", "threadq"}, 5 * time.Second}, // Turn on threadq and wait 5 sec
{[]string{"k", "cpu", "sev"}, 5 * time.Second}, // Send a SEV and wait 5 sec
{[]string{"k", "threadload"}, 200 * time.Millisecond}, // Turn off threadload
{[]string{"k", "threadq"}, 0}, // Turn off threadq
// Invoke the threads command twice to identify hanging processes.
{[]string{"threads", "--all-processes"}, 5 * time.Minute},
{[]string{"threads", "--all-processes"}, 5 * time.Minute},
}
// Open opens a new serial port using defaults.
func Open(name string) (io.ReadWriteCloser, error) {
return OpenWithOptions(name, defaultBaudRate)
}
// OpenWithOptions opens a new serial port with the given name and baud rate.
func OpenWithOptions(name string, baudRate int) (io.ReadWriteCloser, error) {
return open(name, baudRate)
}
// Command contains the command to run over serial and the expected duration
// to wait for the command to complete.
type Command struct {
Cmd []string
SleepDuration time.Duration
}
func asSerialCmd(cmd []string) string {
// The UART kernel driver expects a command to be followed by \r\n.
// Send a leading \r\n for all commands as there may be characters in the buffer already
// that we need to clear first.
return fmt.Sprintf("\r\n%s\r\n", strings.Join(cmd, " "))
}
// NewSocket opens a connection on the provided `socketPath`.
func NewSocket(ctx context.Context, socketPath string) (io.ReadWriteCloser, error) {
if socketPath == "" {
return nil, fmt.Errorf("serialSocketPath not set")
}
socket, err := net.Dial("unix", socketPath)
if err != nil {
return nil, fmt.Errorf("failed to open serial socket connection: %v", err)
}
// Trigger a new cursor print by sending a newline. This may do nothing if the
// system was not ready to process input, but in that case it will print a
// new cursor anyways when it is ready to receive input.
io.WriteString(socket, asSerialCmd([]string{}))
// Look for the cursor, which should indicate that the console is ready for input.
ctx, cancel := context.WithTimeout(ctx, 45*time.Second)
defer cancel()
m := iomisc.NewMatchingReader(socket, [][]byte{[]byte(consoleCursor)})
if _, err = iomisc.ReadUntilMatch(ctx, m); err != nil {
return nil, fmt.Errorf("failed to find cursor: %v", err)
}
return socket, nil
}
// RunCommands writes the provided commands to the serial socket.
func RunCommands(ctx context.Context, socket io.ReadWriteCloser, cmds []Command) error {
for _, cmd := range cmds {
logger.Debugf(ctx, "running over serial: %v", cmd.Cmd)
if _, err := io.WriteString(socket, asSerialCmd(cmd.Cmd)); err != nil {
return fmt.Errorf("failed to write to serial socket: %v", err)
}
if cmd.SleepDuration > 0 {
logger.Debugf(ctx, "sleeping for %v", cmd.SleepDuration)
time.Sleep(cmd.SleepDuration)
}
}
return nil
}
// RunDiagnostics runs a series of diagnostic commands over serial.
func RunDiagnostics(ctx context.Context, socket io.ReadWriteCloser) error {
logger.Debugf(ctx, "attempting to run diagnostics over serial")
return RunCommands(ctx, socket, diagnosticCmds)
}