Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

um: chan: use blocking IO for console output for time-travel

When in time-travel mode (infinite-cpu or external) time should not pass
for writing to the console. As such, it makes sense to put the FD for
the output side into blocking mode and simply let any write to it hang.

If we did not do this, then time could pass waiting for the console to
become writable again. This is not desirable as it has random effects on
the clock between runs.

Implement this by duplicating the FD if output is active in a relevant
mode and setting the duplicate to be blocking. This avoids changing the
input channel to be blocking should it exists. After this, use the
blocking FD for all write operations and do not allocate an IRQ it is
set.

Without time-travel mode fd_out will always match fd_in and IRQs are
registered.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Link: https://patch.msgid.link/20231018123643.1255813-4-benjamin@sipsolutions.net
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

authored by

Benjamin Berg and committed by
Johannes Berg
b2f9b77c 4cfb44df

+74 -21
+2 -1
arch/um/drivers/chan.h
··· 22 22 unsigned int output:1; 23 23 unsigned int opened:1; 24 24 unsigned int enabled:1; 25 - int fd; 25 + int fd_in; 26 + int fd_out; /* only different to fd_in if blocking output is needed */ 26 27 const struct chan_ops *ops; 27 28 void *data; 28 29 };
+61 -20
arch/um/drivers/chan_kern.c
··· 81 81 }; 82 82 #endif /* CONFIG_NOCONFIG_CHAN */ 83 83 84 + static inline bool need_output_blocking(void) 85 + { 86 + return time_travel_mode == TT_MODE_INFCPU || 87 + time_travel_mode == TT_MODE_EXTERNAL; 88 + } 89 + 84 90 static int open_one_chan(struct chan *chan) 85 91 { 86 92 int fd, err; ··· 102 96 return fd; 103 97 104 98 err = os_set_fd_block(fd, 0); 105 - if (err) { 106 - (*chan->ops->close)(fd, chan->data); 107 - return err; 108 - } 99 + if (err) 100 + goto out_close; 109 101 110 - chan->fd = fd; 102 + chan->fd_in = fd; 103 + chan->fd_out = fd; 104 + 105 + /* 106 + * In time-travel modes infinite-CPU and external we need to guarantee 107 + * that any writes to the output succeed immdiately from the point of 108 + * the VM. The best way to do this is to put the FD in blocking mode 109 + * and simply wait/retry until everything is written. 110 + * As every write is guaranteed to complete, we also do not need to 111 + * request an IRQ for the output. 112 + * 113 + * Note that input cannot happen in a time synchronized way. We permit 114 + * it, but time passes very quickly if anything waits for a read. 115 + */ 116 + if (chan->output && need_output_blocking()) { 117 + err = os_dup_file(chan->fd_out); 118 + if (err < 0) 119 + goto out_close; 120 + 121 + chan->fd_out = err; 122 + 123 + err = os_set_fd_block(chan->fd_out, 1); 124 + if (err) { 125 + os_close_file(chan->fd_out); 126 + goto out_close; 127 + } 128 + } 111 129 112 130 chan->opened = 1; 113 131 return 0; 132 + 133 + out_close: 134 + (*chan->ops->close)(fd, chan->data); 135 + return err; 114 136 } 115 137 116 138 static int open_chan(struct list_head *chans) ··· 159 125 void chan_enable_winch(struct chan *chan, struct tty_port *port) 160 126 { 161 127 if (chan && chan->primary && chan->ops->winch) 162 - register_winch(chan->fd, port); 128 + register_winch(chan->fd_in, port); 163 129 } 164 130 165 131 static void line_timer_cb(struct work_struct *work) ··· 190 156 191 157 if (chan->enabled) 192 158 continue; 193 - err = line_setup_irq(chan->fd, chan->input, chan->output, line, 194 - chan); 159 + err = line_setup_irq(chan->fd_in, chan->input, 160 + chan->output && !need_output_blocking(), 161 + line, chan); 195 162 if (err) 196 163 goto out_close; 197 164 ··· 231 196 232 197 if (chan->input && chan->enabled) 233 198 um_free_irq(chan->line->read_irq, chan); 234 - if (chan->output && chan->enabled) 199 + if (chan->output && chan->enabled && 200 + !need_output_blocking()) 235 201 um_free_irq(chan->line->write_irq, chan); 236 202 chan->enabled = 0; 237 203 } ··· 252 216 } else { 253 217 if (chan->input && chan->enabled) 254 218 um_free_irq(chan->line->read_irq, chan); 255 - if (chan->output && chan->enabled) 219 + if (chan->output && chan->enabled && 220 + !need_output_blocking()) 256 221 um_free_irq(chan->line->write_irq, chan); 257 222 chan->enabled = 0; 258 223 } 224 + if (chan->fd_out != chan->fd_in) 225 + os_close_file(chan->fd_out); 259 226 if (chan->ops->close != NULL) 260 - (*chan->ops->close)(chan->fd, chan->data); 227 + (*chan->ops->close)(chan->fd_in, chan->data); 261 228 262 229 chan->opened = 0; 263 - chan->fd = -1; 230 + chan->fd_in = -1; 231 + chan->fd_out = -1; 264 232 } 265 233 266 234 void close_chan(struct line *line) ··· 284 244 void deactivate_chan(struct chan *chan, int irq) 285 245 { 286 246 if (chan && chan->enabled) 287 - deactivate_fd(chan->fd, irq); 247 + deactivate_fd(chan->fd_in, irq); 288 248 } 289 249 290 250 int write_chan(struct chan *chan, const u8 *buf, size_t len, int write_irq) ··· 294 254 if (len == 0 || !chan || !chan->ops->write) 295 255 return 0; 296 256 297 - n = chan->ops->write(chan->fd, buf, len, chan->data); 257 + n = chan->ops->write(chan->fd_out, buf, len, chan->data); 298 258 if (chan->primary) { 299 259 ret = n; 300 260 } ··· 308 268 if (!chan || !chan->ops->console_write) 309 269 return 0; 310 270 311 - n = chan->ops->console_write(chan->fd, buf, len); 271 + n = chan->ops->console_write(chan->fd_out, buf, len); 312 272 if (chan->primary) 313 273 ret = n; 314 274 return ret; ··· 336 296 if (chan && chan->primary) { 337 297 if (chan->ops->window_size == NULL) 338 298 return 0; 339 - return chan->ops->window_size(chan->fd, chan->data, 299 + return chan->ops->window_size(chan->fd_in, chan->data, 340 300 rows_out, cols_out); 341 301 } 342 302 chan = line->chan_out; 343 303 if (chan && chan->primary) { 344 304 if (chan->ops->window_size == NULL) 345 305 return 0; 346 - return chan->ops->window_size(chan->fd, chan->data, 306 + return chan->ops->window_size(chan->fd_in, chan->data, 347 307 rows_out, cols_out); 348 308 } 349 309 return 0; ··· 359 319 (*chan->ops->free)(chan->data); 360 320 361 321 if (chan->primary && chan->output) 362 - ignore_sigio_fd(chan->fd); 322 + ignore_sigio_fd(chan->fd_in); 363 323 kfree(chan); 364 324 } 365 325 ··· 518 478 .output = 0, 519 479 .opened = 0, 520 480 .enabled = 0, 521 - .fd = -1, 481 + .fd_in = -1, 482 + .fd_out = -1, 522 483 .ops = ops, 523 484 .data = data }); 524 485 return chan; ··· 590 549 schedule_delayed_work(&line->task, 1); 591 550 goto out; 592 551 } 593 - err = chan->ops->read(chan->fd, &c, chan->data); 552 + err = chan->ops->read(chan->fd_in, &c, chan->data); 594 553 if (err > 0) 595 554 tty_insert_flip_char(port, c, TTY_NORMAL); 596 555 } while (err > 0);
+1
arch/um/include/shared/os.h
··· 163 163 extern int os_accept_connection(int fd); 164 164 extern int os_create_unix_socket(const char *file, int len, int close_on_exec); 165 165 extern int os_shutdown_socket(int fd, int r, int w); 166 + extern int os_dup_file(int fd); 166 167 extern void os_close_file(int fd); 167 168 extern int os_rcv_fd(int fd, int *helper_pid_out); 168 169 extern int os_connect_socket(const char *name);
+10
arch/um/os-Linux/file.c
··· 240 240 return err; 241 241 } 242 242 243 + int os_dup_file(int fd) 244 + { 245 + int new_fd = dup(fd); 246 + 247 + if (new_fd < 0) 248 + return -errno; 249 + 250 + return new_fd; 251 + } 252 + 243 253 void os_close_file(int fd) 244 254 { 245 255 close(fd);