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
19 typedef 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
26 static zx_device_t* pty_root;
27
28 #define psd_from_ps(ps) containerof(ps, pty_server_dev_t, srv)
29
psd_recv(pty_server_t * ps,const void * data,size_t len,size_t * actual)30 static 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
psd_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual)50 static 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
psd_write(void * ctx,const void * buf,size_t count,zx_off_t off,size_t * actual)81 static 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
psd_ioctl(void * ctx,uint32_t op,const void * in_buf,size_t in_len,void * out_buf,size_t out_len,size_t * out_actual)95 static 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.
117 static 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
ptmx_open(void * ctx,zx_device_t ** out,uint32_t flags)130 static 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
161 static zx_protocol_device_t ptmx_ops = {
162 .version = DEVICE_OPS_VERSION,
163 .open = ptmx_open,
164 };
165
ptmx_bind(void * ctx,zx_device_t * parent)166 static 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
176 static zx_driver_ops_t ptmx_driver_ops = {
177 .version = DRIVER_OPS_VERSION,
178 .bind = ptmx_bind,
179 };
180
181 ZIRCON_DRIVER_BEGIN(ptmx, ptmx_driver_ops, "zircon", "0.1", 1)
182 BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
183 ZIRCON_DRIVER_END(ptzx)
184