1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <threads.h>
9
10#include <ddk/device.h>
11#include <ddk/driver.h>
12#include <ddk/binding.h>
13
14#include "pty-core.h"
15#include "pty-fifo.h"
16
17#include <zircon/device/pty.h>
18
19typedef struct pty_server_dev {
20    pty_server_t srv;
21
22    mtx_t lock;
23    pty_fifo_t fifo;
24} pty_server_dev_t;
25
26static zx_device_t* pty_root;
27
28#define psd_from_ps(ps) containerof(ps, pty_server_dev_t, srv)
29
30static zx_status_t psd_recv(pty_server_t* ps, const void* data, size_t len, size_t* actual) {
31    if (len == 0) {
32        return 0;
33    }
34
35    pty_server_dev_t* psd = psd_from_ps(ps);
36
37    bool was_empty = pty_fifo_is_empty(&psd->fifo);
38    *actual = pty_fifo_write(&psd->fifo, data, len, false);
39    if (was_empty && *actual) {
40        device_state_set(ps->zxdev, DEV_STATE_READABLE);
41    }
42
43    if (*actual == 0) {
44        return ZX_ERR_SHOULD_WAIT;
45    } else {
46        return ZX_OK;
47    }
48}
49
50static zx_status_t psd_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
51    pty_server_dev_t* psd = ctx;
52
53    bool eof = false;
54
55    mtx_lock(&psd->srv.lock);
56    bool was_full = pty_fifo_is_full(&psd->fifo);
57    size_t length = pty_fifo_read(&psd->fifo, buf, count);
58    if (pty_fifo_is_empty(&psd->fifo)) {
59        if (list_is_empty(&psd->srv.clients)) {
60            eof = true;
61        } else {
62            device_state_clr(psd->srv.zxdev, DEV_STATE_READABLE);
63        }
64    }
65    if (was_full && length) {
66        pty_server_resume_locked(&psd->srv);
67    }
68    mtx_unlock(&psd->srv.lock);
69
70    if (length > 0) {
71        *actual = length;
72        return ZX_OK;
73    } else if (eof) {
74        *actual = 0;
75        return ZX_OK;
76    } else {
77        return ZX_ERR_SHOULD_WAIT;
78    }
79}
80
81static zx_status_t psd_write(void* ctx, const void* buf, size_t count, zx_off_t off,
82                             size_t* actual) {
83    pty_server_dev_t* psd = ctx;
84    size_t length;
85    zx_status_t status;
86
87    if ((status = pty_server_send(&psd->srv, buf, count, false, &length)) < 0) {
88        return status;
89    } else {
90        *actual = length;
91        return ZX_OK;
92    }
93}
94
95static zx_status_t psd_ioctl(void* ctx, uint32_t op,
96                  const void* in_buf, size_t in_len,
97                  void* out_buf, size_t out_len, size_t* out_actual) {
98    pty_server_dev_t* psd = ctx;
99
100    switch (op) {
101    case IOCTL_PTY_SET_WINDOW_SIZE: {
102        const pty_window_size_t* wsz = in_buf;
103        if (in_len != sizeof(pty_window_size_t)) {
104            return ZX_ERR_INVALID_ARGS;
105        }
106        pty_server_set_window_size(&psd->srv, wsz->width, wsz->height);
107        return ZX_OK;
108    }
109    default:
110        return ZX_ERR_NOT_SUPPORTED;
111    }
112}
113
114// Since we have no special functionality,
115// we just use the implementations from pty-core
116// directly.
117static zx_protocol_device_t psd_ops = {
118    .version = DEVICE_OPS_VERSION,
119    // .open = default, allow cloning
120    .open_at = pty_server_openat,
121    .release = pty_server_release,
122    .read = psd_read,
123    .write = psd_write,
124    .ioctl = psd_ioctl,
125};
126
127
128// ptmx device - used to obtain the pty server of a new pty instance
129
130static zx_status_t ptmx_open(void* ctx, zx_device_t** out, uint32_t flags) {
131    pty_server_dev_t* psd;
132    if ((psd = calloc(1, sizeof(pty_server_dev_t))) == NULL) {
133        return ZX_ERR_NO_MEMORY;
134    }
135
136    pty_server_init(&psd->srv);
137    psd->srv.recv = psd_recv;
138    mtx_init(&psd->lock, mtx_plain);
139    psd->fifo.head = 0;
140    psd->fifo.tail = 0;
141
142    device_add_args_t args = {
143        .version = DEVICE_ADD_ARGS_VERSION,
144        .name = "pty",
145        .ctx = psd,
146        .ops = &psd_ops,
147        .proto_id = ZX_PROTOCOL_PTY,
148        .flags = DEVICE_ADD_INSTANCE,
149    };
150
151    zx_status_t status;
152    if ((status = device_add(pty_root, &args, &psd->srv.zxdev)) < 0) {
153        free(psd);
154        return status;
155    }
156
157    *out = psd->srv.zxdev;
158    return ZX_OK;
159}
160
161static zx_protocol_device_t ptmx_ops = {
162    .version = DEVICE_OPS_VERSION,
163    .open = ptmx_open,
164};
165
166static zx_status_t ptmx_bind(void* ctx, zx_device_t* parent) {
167    device_add_args_t args = {
168        .version = DEVICE_ADD_ARGS_VERSION,
169        .name = "ptmx",
170        .ops = &ptmx_ops,
171    };
172
173    return device_add(parent, &args, &pty_root);
174}
175
176static zx_driver_ops_t ptmx_driver_ops = {
177    .version = DRIVER_OPS_VERSION,
178    .bind = ptmx_bind,
179};
180
181ZIRCON_DRIVER_BEGIN(ptmx, ptmx_driver_ops, "zircon", "0.1", 1)
182    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
183ZIRCON_DRIVER_END(ptzx)
184