sim.blackboxes: add serial blackbox, with a serial_pty driver.
[lambdasoc.git] / lambdasoc / sim / blackboxes / serial / drivers / serial_pty / serial_pty.cc
1 #include <cassert>
2 #include <fcntl.h>
3 #include <iostream>
4 #include <map>
5 #include <memory>
6 #include <poll.h>
7 #include <stdexcept>
8 #include <string>
9 #include <termios.h>
10 #include <unistd.h>
11 #include <util/log_fmt.h>
12 #include <vector>
13
14 struct pty_file {
15 const int fd;
16
17 pty_file()
18 : fd(posix_openpt(O_RDWR | O_NOCTTY)) {
19 if (fd < 0) {
20 throw std::runtime_error(fmt_errno("posix_openpt"));
21 }
22 }
23
24 ~pty_file() {
25 close(fd);
26 }
27
28 void prepare() const {
29 if (grantpt(fd)) {
30 throw std::runtime_error(fmt_errno("grantpt"));
31 }
32 if (unlockpt(fd)) {
33 throw std::runtime_error(fmt_errno("unlockpt"));
34 }
35 struct termios raw;
36 if (tcgetattr(fd, &raw)) {
37 throw std::runtime_error(fmt_errno("tcgetattr"));
38 }
39 raw.c_cflag = (raw.c_cflag & ~CSIZE) | CS8;
40 raw.c_lflag &= ~(ECHO | ICANON);
41 if (tcsetattr(fd, TCSANOW, &raw)) {
42 throw std::runtime_error(fmt_errno("tcsetattr"));
43 }
44 }
45
46 bool readable() const {
47 pollfd pfd = {fd, POLLIN, 0};
48 poll(&pfd, /*nfds=*/1, /*timeout=*/0);
49 return (pfd.revents & POLLIN);
50 }
51
52 bool writable() const {
53 pollfd pfd = {fd, POLLOUT, 0};
54 poll(&pfd, /*nfds=*/1, /*timeout=*/0);
55 return (pfd.revents & POLLOUT);
56 }
57
58 unsigned char read_char() const {
59 unsigned char c;
60 ssize_t nread = read(fd, &c, /*count=*/1);
61 if (nread != 1) {
62 throw std::runtime_error(fmt_errno("read"));
63 }
64 return c;
65 }
66
67 void write_char(unsigned char c) const {
68 ssize_t nwrite = write(fd, &c, /*count=*/1);
69 if (nwrite != 1) {
70 throw std::runtime_error(fmt_errno("write"));
71 }
72 }
73 };
74
75 struct serial_pty;
76 static std::map<const std::string, std::weak_ptr<serial_pty>> serial_pty_map;
77
78 struct serial_pty {
79 protected:
80 bool _has_rx;
81 bool _has_tx;
82
83 public:
84 const std::string id;
85 const pty_file pty;
86
87 serial_pty(const std::string &id)
88 : _has_rx(false)
89 , _has_tx(false)
90 , id(id)
91 , pty() {
92 pty.prepare();
93 }
94
95 serial_pty() = delete;
96 serial_pty(const serial_pty &) = delete;
97 serial_pty &operator=(const serial_pty &) = delete;
98
99 ~serial_pty() {
100 if (serial_pty_map.count(id)) {
101 serial_pty_map.erase(id);
102 }
103 }
104
105 static std::shared_ptr<serial_pty> get(const std::string &id) {
106 std::shared_ptr<serial_pty> desc;
107 if (!serial_pty_map.count(id)) {
108 desc = std::make_shared<serial_pty>(id);
109 serial_pty_map[id] = desc;
110 } else {
111 desc = serial_pty_map[id].lock();
112 assert(desc);
113 }
114 return desc;
115 }
116
117 void set_rx() {
118 _has_rx = true;
119 }
120 void set_tx() {
121 _has_tx = true;
122 }
123
124 bool has_rx() const {
125 return _has_rx;
126 }
127 bool has_tx() const {
128 return _has_tx;
129 }
130 };
131
132 namespace cxxrtl_design {
133
134 // Receiver
135
136 struct serial_pty_rx : public bb_p_serial__rx</*DATA_BITS=*/8> {
137 std::shared_ptr<serial_pty> desc;
138 std::vector<unsigned char> buffer;
139
140 serial_pty_rx(const std::shared_ptr<serial_pty> &desc)
141 : desc(desc) {
142 if (desc->has_rx()) {
143 throw std::invalid_argument(fmt_msg("RX port collision"));
144 }
145 desc->set_rx();
146 }
147
148 void reset() override {}
149
150 bool eval() override {
151 if (posedge_p_clk()) {
152 if (p_ack.get<bool>() & p_rdy.curr.get<bool>()) {
153 assert(!buffer.empty());
154 buffer.erase(buffer.begin());
155 p_rdy.next.set<bool>(false);
156 }
157 if (desc->pty.readable()) {
158 buffer.insert(buffer.end(), desc->pty.read_char());
159 }
160 if (!buffer.empty()) {
161 p_rdy.next.set<bool>(true);
162 p_data.next.set<unsigned char>(buffer.front());
163 }
164 }
165 return bb_p_serial__rx</*DATA_BITS=*/8>::eval();
166 }
167 };
168
169 template<>
170 std::unique_ptr<bb_p_serial__rx</*DATA_BITS=*/8>>
171 bb_p_serial__rx</*DATA_BITS=*/8>::create(std::string name, cxxrtl::metadata_map parameters,
172 cxxrtl::metadata_map attributes) {
173 assert(parameters.count("ID"));
174 const std::string &id = parameters["ID"].as_string();
175
176 std::shared_ptr<serial_pty> desc = serial_pty::get(id);
177 std::cout << "Assigning '" << name << "' to " << ptsname(desc->pty.fd) << "\n";
178
179 return std::make_unique<serial_pty_rx>(desc);
180 }
181
182 // Transmitter
183
184 struct serial_pty_tx : public bb_p_serial__tx</*DATA_BITS=*/8> {
185 const std::shared_ptr<serial_pty> desc;
186
187 serial_pty_tx(const std::shared_ptr<serial_pty> &desc)
188 : desc(desc) {
189 if (desc->has_tx()) {
190 throw std::invalid_argument(fmt_msg("TX port collision"));
191 }
192 desc->set_tx();
193 }
194
195 void reset() override {}
196
197 bool eval() override {
198 if (posedge_p_clk()) {
199 if (p_ack.get<bool>() & p_rdy.curr.get<bool>()) {
200 desc->pty.write_char(p_data.get<unsigned char>());
201 }
202 p_rdy.next.set<bool>(desc->pty.writable());
203 }
204 return bb_p_serial__tx</*DATA_BITS=*/8>::eval();
205 }
206 };
207
208 template<>
209 std::unique_ptr<bb_p_serial__tx</*DATA_BITS=*/8>>
210 bb_p_serial__tx</*DATA_BITS=*/8>::create(std::string name, cxxrtl::metadata_map parameters,
211 cxxrtl::metadata_map attributes) {
212 assert(parameters.count("ID"));
213 const std::string &id = parameters["ID"].as_string();
214
215 std::shared_ptr<serial_pty> desc = serial_pty::get(id);
216 std::cout << "Assigning '" << name << "' to " << ptsname(desc->pty.fd) << "\n";
217
218 return std::make_unique<serial_pty_tx>(desc);
219 }
220
221 } // namespace cxxrtl_design