# Virtio-console ###### tags: `virtualization` --- ## TTY * Flow chart * tty core (tty_io.*) * tty line discipline (n_tty.*) * tty driver (e.g. serial_core, hvc...) * console * serial port * pty (pseudotty) ![](https://i.imgur.com/9vs6MNe.png) * reference :LDD3,ch18. ## virtio console * Flow chart * console * serial port ``` User space +-----+--------------------------+------------------- vfs ^ ^ kernel space | | | +--------------+-----------------+ | | | | | tty | | | | | +--------------------------------+ | | | | | hvc(hotplug virtual console) | | | | | +-----------------^--------------+ | | +-----+---------+ | | port fops +---------+---------v--------------+ +- - - - - - - -+ | hvc ops | | rproc serial | +------------------------+ +---------------+ | | Virtue-console | +--------------------------------------------------+ ``` ## virtio console operation --- ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this init->register_virtio->virtio_console,virtio_serial } ``` ```clike= static struct virtio_driver virtio_console = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .probe = virtcons_probe, .remove = virtcons_remove, .config_changed = config_intr, #ifdef CONFIG_PM_SLEEP .freeze = virtcons_freeze, .restore = virtcons_restore, #endif }; static struct virtio_driver virtio_rproc_serial = { .feature_table = rproc_serial_features, .feature_table_size = ARRAY_SIZE(rproc_serial_features), .driver.name = "virtio_rproc_serial", .driver.owner = THIS_MODULE, .id_table = rproc_serial_id_table, .probe = virtcons_probe, .remove = virtcons_remove, }; ``` ### Prob virtio-console device ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block]//, style=dashed] //All the lines look like this //graph [splines=ortho] "is multiport ?"[shape=diamond]; "is rproc serial"[shape=diamond]; subgraph prob{ lable = probdev style = filled; fillcolor = block; "virtio_bus matched"[shape=circle]; "is_rproc_serial(vdev)" [shape=diamond]; "match?"[shape=diamond]; "virtio_bus matched"->"vdrv->prob(vdev)"->"register a chrdev for portdev"->"is_rproc_serial(vdev)"; "is_rproc_serial(vdev)"->"read feature bits for multiport"[label = "no"] "is_rproc_serial(vdev)"->"init virtqueues"[label = "yes"] "read feature bits for multiport"->"match?"; "match?"->"init virtqueues"[label = "no"]; "match?"->"set multiport flag"[label = "yes"]; "set multiport flag"->"init virtqueues" "init virtqueues"->"init portdev" } "init virtqueues"->"is multiport?"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; subgraph init_vqs{ label = init_vqs1 "is multiport?"[shape=diamond]; "is multiport?"->"alloc (ports+1)*2 virtqueue buffer"[label = "yes"]; "is multiport?"->"alloc 2 virtqueue buffer"[label = "no"]; "alloc (ports+1)*2 virtqueue buffer"->"put control_intr() into io_callback"->"put in/out_intr() into io_callback"; "alloc 2 virtqueue buffer"->"put in/out_intr() into io_callback"->"rproc_virtio_find_vqs()"; "rproc_virtio_find_vqs()"->"rproc_alloc_vring"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "rproc_alloc_vring"->"vring_new_virtqueue()"->"using rvring to create vring_vritqueue \nand add rproc_virtio_notify \nand io_callback into vring_vritqueue->vq\nreturn vring_vritqueue->vq"->"init vring descriptor" "rproc_virtio_find_vqs()"->"assign vq to portdev->*vqs[*]" } "init portdev"->"virtio_device_ready"; "virtio_device_ready"->"rproc_virtio_set_status\nVIRTIO_CONFIG_S_DRIVER_OK"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "virtio_device_ready"->"assign config/control _work_handler() to\n portdev->config/control _work"->"is multiport ?" "is multiport ?"->"add_port()"[label="no"]; "is multiport ?"->"fill_queue()"[label="yes"]; "fill_queue()"->"alloc_buf()"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "alloc_buf()"->"is rproc serial"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "is rproc serial"->"get_device(platform-dev)"[label = "yes"]; "get_device(platform-dev)"->"dma_alloc_coherent()"; "is rproc serial"->"kmalloc()"[label = "no"]; "kmalloc()"->"return port_buf"; "dma_alloc_coherent()"->"return port_buf" "alloc_buf()"->"add_inbuf()" "add_inbuf()"->"init sglist"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "init sglist"->"virtqueue_add_inbuf"->virtqueue_add "is indirect and multibuffer?"[shape=diamond]; virtqueue_add->"is indirect and multibuffer?" "is indirect and multibuffer?"->"alloc_indirect()"[label = "yes"] "is indirect and multibuffer?"->"setup descriptor"[label = "no"] "alloc_indirect()"->"setup descriptor" "fill_queue()"->"add portdev to portdriver_data_portdev tail" "add_port()"->"add portdev to portdriver_data_portdev tail" "add_port()"->"create port"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "create port"->"init port"->"fill_queue & \n if not rproc serial and not multiport,\n init port console.\nhvc_alloc() and\n send_control_msg(VIRTIO_CONSOLE_PORT_OPEN)"->"send control msg \n(VIRTIO_CONSOLE_PORT_READY)"->"add port to portdriver_data_portdev tail" "add portdev to portdriver_data_portdev tail"->"if multiport and early console plz wait hvc matched" {rank=same;"create port","add_port()"} {rank=same;"fill_queue()","alloc_buf()"} {rank=same;"init sglist","add_inbuf()"} {rank=same;"is rproc serial","alloc_buf()"} {rank=same;"init virtqueues","is multiport?"} {rank=same;"rproc_virtio_find_vqs()","rproc_alloc_vring"} {rank=same;"virtio_device_ready","rproc_virtio_set_status\nVIRTIO_CONFIG_S_DRIVER_OK"}// Put them on the same level } ``` * config_ops is set by remoteproc_virtio ### How to read data from the port? ```clike= /* * after add_port() * port->cdev->ops=&port_fops; */ static const struct file_operations port_fops = { .owner = THIS_MODULE, .open = port_fops_open, .read = port_fops_read, .write = port_fops_write, .splice_write = port_fops_splice_write, .poll = port_fops_poll, .release = port_fops_release, .fasync = port_fops_fasync, .llseek = no_llseek, }; ``` * Firstly, we focus what the thing before file read * look into virtqueue callback * when it did the `init_vqs()`, callback has been assigned `in_intr()` and `out_intr()` ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "has in_buffer?"[shape=diamond]; find_port->"get the port's in_buffer"; "get the port's in_buffer"->"has in_buffer?"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "has in_buffer?"->"return inbuf"[label=yes] "has in_buffer?"->"virtqueue_get_buf"[label=no] "virtqueue_get_buf"->"get last used idx"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "get last used idx"->detach_buf "get the port's in_buffer"->"send SIGIO to port"->"wake up interruptible for port->waitqueue"->"if its hvc, kick hvc" {rank=same;"get the port's in_buffer","has in_buffer?"} {rank=same;"get last used idx","virtqueue_get_buf"} } ``` * Read it! ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this port_has_data[shape=diamond]; "Block or nonblock"[shape=diamond]; "consume buffer?"[shape=diamond]; "user?"[shape=diamond]; port_has_data->"Block or nonblock"[label = no]; port_has_data->"fill_readbuf()"[label = yes]; "Block or nonblock"->"return -EAGAIN"[label = nonblocking]; "Block or nonblock"->"wait event freezable\ncondition is !will_read_block()\n(port has data or host connected)"[label = blocking]; "wait event freezable\ncondition is !will_read_block()\n(port has data or host connected)"->"fill_readbuf()" "fill_readbuf()"->"user?"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "user?"->"copy to user"[label=yes] "user?"->"memory copy"[label=no] {"copy to user","memory copy"}->"consume buffer?" "consume buffer?"->"finish"[label=no] "consume buffer?"->"add_inbuf"[label=yes] "add_inbuf"->"finish" {rank=same;"fill_readbuf()","user?"} } ``` * Secondly, if the reader is tty * The tty will call hvc->get_char() to read buffer ```c /* The operations for console ports. */ static const struct hv_ops hv_ops = { .get_chars = get_chars, .put_chars = put_chars, .notifier_add = notifier_add_vio, .notifier_del = notifier_del_vio, .notifier_hangup = notifier_del_vio, }; ``` ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "find_port_by_vtermno"->"get the console's port"->"fill_readbuf()"; "find_port_by_vtermno"->"list for each entry \n(*console, pdrvdata.consoles, list)"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; {rank=same;"list for each entry \n(*console, pdrvdata.consoles, list)","find_port_by_vtermno"} } ``` ```clike= /* * This is a global struct for storing common data for all the devices * this driver handles. * * Mainly, it has a linked list for all the consoles in one place so * that callbacks from hvc for get_chars(), put_chars() work properly * across multiple devices and multiple ports per device. */ struct ports_driver_data { /* Used for registering chardevs */ struct class *class; /* Used for exporting per-port information to debugfs */ struct dentry *debugfs_dir; /* List of all the devices we're handling */ struct list_head portdevs; /* * This is used to keep track of the number of hvc consoles * spawned by this driver. This number is given as the first * argument to hvc_alloc(). To correctly map an initial * console spawned via hvc_instantiate to the console being * hooked up via hvc_alloc, we need to pass the same vtermno. * * We also just assume the first console being initialised was * the first one that got used as the initial console. */ unsigned int next_vtermno; /* All the console devices handled by this driver */ struct list_head consoles; }; ``` ```clike= /* This struct holds the per-port data */ struct port { /* Next port in the list, head is in the ports_device */ struct list_head list; /* Pointer to the parent virtio_console device */ struct ports_device *portdev; /* The current buffer from which data has to be fed to readers */ struct port_buffer *inbuf; /* * To protect the operations on the in_vq associated with this * port. Has to be a spinlock because it can be called from * interrupt context (get_char()). */ spinlock_t inbuf_lock; /* Protect the operations on the out_vq. */ spinlock_t outvq_lock; /* The IO vqs for this port */ struct virtqueue *in_vq, *out_vq; /* File in the debugfs directory that exposes this port's information */ struct dentry *debugfs_file; /* * Keep count of the bytes sent, received and discarded for * this port for accounting and debugging purposes. These * counts are not reset across port open / close events. */ struct port_stats stats; /* * The entries in this struct will be valid if this port is * hooked up to an hvc console */ struct console cons; /* Each port associates with a separate char device */ struct cdev *cdev; struct device *dev; /* Reference-counting to handle port hot-unplugs and file operations */ struct kref kref; /* A waitqueue for poll() or blocking read operations */ wait_queue_head_t waitqueue; /* The 'name' of the port that we expose via sysfs properties */ char *name; /* We can notify apps of host connect / disconnect events via SIGIO */ struct fasync_struct *async_queue; /* The 'id' to identify the port with the Host */ u32 id; bool outvq_full; /* Is the host device open */ bool host_connected; /* We should allow only one process to open a port */ bool guest_connected; }; ``` * OK, let's get down to the port fops. * What's about write data? * The writing virtqueue also has callback but we go through `port_fops_write` first. ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "Block or nonblock"[shape=diamond]; "Block or nonblock?"[shape=diamond]; "will_write_block()"[shape=diamond]; "wait_port_writable()"->"will_write_block()"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "will_write_block()"->"free used buf and get full flag"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "will_write_block()"->"Block or nonblock"[label = yes]; "will_write_block()"->"return 0"[label = no]; "Block or nonblock"->"return -EAGAIN,\n try again"[label = nonblock] "Block or nonblock"->"wait event freezable\ncondition is !will_write_block()"[label = block] "wait_port_writable()"->"if write size is more than 2^15, cutoff to 2^15."->"alloc_buf()"->"copy from user"->"init sg list for the buf page"->"send the sg_list to the port" "send the sg_list to the port"->"virtqueue_add_outbuf()"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "virtqueue_add_outbuf()"->"virtqueue_add()"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "virtqueue_add_outbuf()"->"virtqueue kick" "virtqueue kick"->"Block or nonblock?" "Block or nonblock?"->"done"[label = nonblock]; "Block or nonblock?"->"wait the date is pushed out"[label = block]; {rank=same;"wait_port_writable()","will_write_block()","free used buf and get full flag"} {rank=same;"send the sg_list to the port","virtqueue_add_outbuf()"} {rank=same;"virtqueue_add()","virtqueue_add_outbuf()"} } ``` * The task is waiting for the vq will be free by out_vq callback, `out_intr`. * When the remote consumer consumes the vq, it will kick the vq. And the callback will be hooked up. ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "out_intr"->"wake up interruptible task\n in port->waitqueue" } ``` * fops has general poll and faync. * poll ```clike= static unsigned int port_fops_poll(struct file *filp, poll_table *wait) { struct port *port; unsigned int ret; port = filp->private_data; poll_wait(filp, &port->waitqueue, wait); if (!port->guest_connected) { /* Port got unplugged */ return POLLHUP; } ret = 0; if (!will_read_block(port)) ret |= POLLIN | POLLRDNORM; if (!will_write_block(port)) ret |= POLLOUT; if (!port->host_connected) ret |= POLLHUP; return ret; } ``` * fasync(asynchronous notification) ```clike= static int port_fops_fasync(int fd, struct file *filp, int mode) { struct port *port; port = filp->private_data; return fasync_helper(fd, filp, mode, &port->async_queue); } ``` * How to use? * write the signal handler to process `SIGIO` * use `sigaction` to register signal handler(you also can use multi-thread to handle it) * write the pid to f_owner with fcntl (if pid is negative, signal will send to group of the pid) * if you have multi-fd or read/write, please using `epoll` to know where send the signal * fops_open ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "connected?"[shape=diamond]; "find port by devt"->"put the port to filp' private_data"->"connected?" "connected?"->"kfer_put() \nreturn BUSY"[label = yes] "connected?"->"set connected"[label = no] "set connected"->"reclaim consumed buffer"->"use nonseekable_open() for the\n inode and filp"->"send VIRTIO_CONSOLE_PORT_OPEN msg" "find port by devt"->"list for each entry for each port device"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "list for each entry for each port device"->"if matched, use kref_get() to add \nreference number of times" {rank=same;"find port by devt","list for each entry for each port device"} } ``` * About DMA * `struct list_head pending_free_dma_bufs` * collect dma unsed buffer * global variable * reclaim_dma_bufs(void) ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "empty(pending_free_dma_bufs)"[shape=diamond]; "is_rproc_serial"[shape=diamond]; "can sleep?"[shape=diamond];"empty(pending_free_dma_bufs)"->"done"[label = yes] "empty(pending_free_dma_bufs)"->"list_cut_position\n(temp,pending_free_dma_bufs,pending_free_dma_bufs.prev)"[label = no] "list_cut_position\n(temp,pending_free_dma_bufs,pending_free_dma_bufs.prev)"->"pending_free_dma_bufs"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "temp"->"pending_free_dma_bufs.next"[label = next] "pending_free_dma_bufs.prev"->"temp"[label = prev] "temp"->"pending_free_dma_bufs.prev"[label = next] "pending_free_dma_bufs.next"->"temp"[label = prev] "pending_free_dma_bufs"->"pending_free_dma_bufs.prev"[label = X]; "pending_free_dma_bufs.next"->"pending_free_dma_bufs"[label = X]; "list_cut_position\n(temp,pending_free_dma_bufs,pending_free_dma_bufs.prev)"->"list for each entry safe"->"delete list(pop the list)"->"free_buf(buf,can_sleep)\nbuf is the list owner" "free_buf(buf,can_sleep)\nbuf is the list owner"->"free_buf()"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "free_buf()"->"put sg_page (free)"->"is_rproc_serial" "is_rproc_serial"->"kfree()"[label = no] "is_rproc_serial"->"can sleep?"[label = yes] "can sleep?"->"dma_free_coherent"[label = yes] "can sleep?"->"list_ad_tail(list,pending_free_dma_bufs)\nreturn; queue up and freed later"[label = no] {rank=same;"list_cut_position\n(temp,pending_free_dma_bufs,pending_free_dma_bufs.prev)","pending_free_dma_bufs"} {rank=same;"free_buf(buf,can_sleep)\nbuf is the list owner","free_buf()"} } } ``` ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "list_cut_position(list,head,entry)"->"head"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "head"->"head.next"[label=" X next"] "head.next"->"head"[label=" X prev"] "head.next"->"..." "..."->"head.next" "..."->"entry"[label=next] "entry"->"..."[label=prev] "entry"->"entry.next"[label=" X next"] "entry"->"list"[label=next] "entry.next"->"head"[label = prev] "entry.next"->"entry"[label=" X prev"] "list"->"head.next"[label=next] "head.next"->"list"[label=prev] "list"->"entry"[label=prev] "head"->"entry.next"[label = next] {rank=same;"head","...","entry","head.next"} {rank=same;"list_cut_position(list,head,entry)","list"} } ``` ## modem uart * plat uart api ```clike= Plat_UART_Init(); INT32 Plat_UART_InitWithType(UART_Port_e port, const char *type); /* follow LM3S6965EVB, return UARTERR_NOERR; * virtio-serial is not a real uart * start bit = none * stop bit = none * parity = none * baud rate = any * data bits = DATABITS 8 * UARTErr_e Plat_UART_GetCfg(UART_Port_e port, UART_Cfg_t *pCfg); * UARTErr_e Plat_UART_SetCfg(UART_Port_e port, UART_Cfg_t *pCfg); * UARTErr_e Plat_UART_Reset(UART_Port_e port); * UARTErr_e Plat_UART_SET_BAUDRATE(UART_Port_e port, UINT32 Baudrate); */ INT32 Plat_UART_ReadData(UART_Port_e port, UINT8* buf, UINT32 buflen); INT32 Plat_UART_WriteData(UART_Port_e port, const UINT8 *buf, UINT32 buflen); void Plat_UART_LOOPBACK_MODE(UART_Port_e port); /* * disable uart_port# interrupt * busy read/write data (while loop) * enable uart_port# interrupt */ INT32 Plat_UART_ReadData_Polling(UART_Port_e port, UINT8* buf, UINT32 buflen); INT32 Plat_UART_WriteData_Polling(UART_Port_e port, UINT8* buf, UINT32 buflen); void Plat_UART_EXIT_Polling(UART_Port_e port); ``` * How to * Create */driverlib/uart_virtiouart.c * for virtio-serial * Modify */plat/src/plat_uart.c * do not modify `/inc/` * if `port_number >= UART_NUM and port_number < VIRTIO_UART_NUM` * using `uart_virtiouart.c` ``` +-------------------------+ +-------------------------+ | | | | | Plat_Uart | | Plat_Uart | +-------------------------+ +-------------------------+ | | | | | target plat_uart | | target plat_uart | +--+--------------------+-+ +--+--------------------+-+ | | +------> | | | Uart driver lib | | virtio-uart lib | | | | | +--+ +-+ +-+ +-+ ++ +--+ +-+ +-+ +-+ ++ | Uart internal api | | | | | | | VirtIO | +-------|------------+ +--------------------+ v Hardware ``` ## RX callback flowchart ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "While loop (1)"[shape=diamond]; "out_vring num >0"[shape=diamond]; "While loop for out_vring"[shape=diamond]; "rx_buff full ?"[shape=diamond]; "While loop (1)"->"reset vq buff"->"rx_buff full ?" "rx_buff full ?"->"Vq_Pop"[label =" has room"] "rx_buff full ?"->"wakeup thread(osSignalSet)"[label =" full"] "wakeup thread(osSignalSet)"->"While loop (1)"[label = try_again] "Vq_Pop"->"out_vring num >0" "out_vring num >0"->"While loop for out_vring"[label = " > 0"] "out_vring num >0"->"break"[label= "equal 0"] "While loop for out_vring"->"fill rx_buffer"[label = " > 0"] "fill rx_buffer"->"While loop for out_vring" "While loop for out_vring"->"Vq_Push"[label= "equal 0"] "Vq_Push"->"While loop (1)" "break"->"total receive \nlength is more than 0"->"Virtio Notify"->"Wakeup thread(osSingnalSet)"->"end" {rank=same;"fill rx_buffer","Vq_Push","break"} } ``` --- ## Failed address * Over memory limited address * same situation in send and receive ``` +---------------+ +-------------------+ | Linux | | Modem | | virtio console+--------+ | virtio uart | | | | | ^ | | | Write len = 100 | | | | | addr = 0xA10000000 | |Get len = 100 | | | | |addr = 0xA1000000 +---------------+ | +-------------------+ | | |---------------------+--|--------------------------+----------|-------+ | | | ... virtqueue | | | | | +------> addr +----------------------+ | | | +-------> len +----------------------+ | | | ... | | | | | | +---------------------+-----------------------------+------------------+ ``` 1. `/* Device we got DMA memory from */` (rproc->parent) 2. ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "is rproc serial"[shape=diamond]; "alloc_buf()"->"is rproc serial"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "is rproc serial"->"get_device(platform-dev)"[label = "yes"]; "get_device(platform-dev)"->"dma_alloc_coherent()"; "is rproc serial"->"kmalloc()"[label = "no"]; "kmalloc()"->"return port_buf"; "dma_alloc_coherent()"->"return port_buf" } ``` ```graphviz digraph hierarchy { nodesep=2.0 // increases the separation between nodes node [color=Gary,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Block, style=dashed] //All the lines look like this "dev has archdata.dma.ops ?"[shape=diamond]; "dev->dma_mem ?"[shape=diamond]; "is_coherent or nommu"[shape=diamond]; "dma_alloc_coherent"->"dev has archdata.dma.ops ?"[dir="forward",arrowhead="none",arrowtail="normal",style=dashed]; "dev has archdata.dma.ops ?"->"uses archdata.dma.ops"[label = "yes"]; "uses archdata.dma.ops"->"archdata.dma.ops->alloc()" "dev has archdata.dma.ops ?"->"arm_dma_ops"[label = "no"]; "arm_dma_ops"->"dev->dma_mem ?"; "dev->dma_mem ?"->"bitmap_find_free_region \nand return mem->virt_base"[label=yes] "dev->dma_mem ?"->"__dma_alloc"[label=no] "__dma_alloc"->"is_coherent or nommu" "is_coherent or nommu"->"simple_buff"[label=yes] "is_coherent or nommu"->"from dma pool ... else if..."[label=no] "from dma pool ... else if..."->"__alloc_remap_buff"->"..."->"dma_alloc_remap\n /* DMA allocation can be \nmapped to user space, so lets\nset VM_USERMAP flags too. */" } ``` 1. Using kmalloc instead of dma_alloc_coherent 2. Setting set_arch_dma_coherent_ops(dev) when rproc is probed * let dma_alloc_coherent select simple_buff ## What's the virtio console name? -> **vport#p#** * first number is virtio device index * last number is the port of the virtio console * In rproc_serial case, it is always be zero * Currently, we just has a virtio_console * the name is `vport5p0` * the name is dynamic by virtio device * the gpsd * it will send a passward to AT and get a certification through the virtio console * is AT supporting by modem side? --- 1. use dynamic name * do not change the linux * flexibility of new virtio console user * the user has to detect by itself 2. static name * modify linux kernel * it is hard for the new user * use vdev config by resource table