static void rpmsg_tty_cb(struct rpmsg_channel *rpdev, void *data, int len, void *priv, u32 src) { int space; unsigned char *cbuf; struct rpmsgtty_port *cport = (struct rpmsgtty_port *)priv; /* flush the recv-ed none-zero data to tty node */ if (len == 0) return; /* pr_info("%s lenrcved=%d\n", __FUNCTION__, len); print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, data, len, true); */ spin_lock_bh(&cport->rx_lock); space = tty_prepare_flip_string(&cport->port, &cbuf, len); if (space <= 0) { dev_err(&rpdev->dev, "No memory for tty_prepare_flip_string\n"); spin_unlock_bh(&cport->rx_lock); return; } if( space != len) pr_err("Trunc buffer %d\n", len-space); memcpy(cbuf, data, space); tty_flip_buffer_push(&cport->port); spin_unlock_bh(&cport->rx_lock); }
static void smd_tty_read(unsigned long param) { unsigned char *ptr; int avail; struct smd_tty_info *info = (struct smd_tty_info *)param; struct tty_struct *tty = info->tty; if (!tty) return; for (;;) { if (test_bit(TTY_THROTTLED, &tty->flags)) break; avail = smd_read_avail(info->ch); if (avail == 0) break; avail = tty_prepare_flip_string(tty, &ptr, avail); if (smd_read(info->ch, ptr, avail) != avail) { printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!"); } wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } tty_wakeup(tty); }
static void smd_tty_notify(void *priv, unsigned event) { unsigned char *ptr; int avail; struct smd_tty_info *info = priv; struct tty_struct *tty = info->tty; if (!tty) return; if (event != SMD_EVENT_DATA) return; for (;;) { if (test_bit(TTY_THROTTLED, &tty->flags)) break; avail = smd_read_avail(info->ch); if (avail == 0) break; avail = tty_prepare_flip_string(tty, &ptr, avail); if (smd_read(info->ch, ptr, avail) != avail) { /* shouldn't be possible since we're in interrupt ** context here and nobody else could 'steal' our ** characters. */ printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!"); } wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } /* XXX only when writable and necessary */ tty_wakeup(tty); }
/* Diag char driver has diag packet ready for userspace */ int tty_diag_channel_write(struct usb_diag_ch *diag_ch, struct diag_request *d_req) { struct diag_tty_data *tty_data = diag_ch->priv_usb; unsigned char *tty_buf; int tty_allocated; unsigned long flags; int cmd_code, subsys_id; /* If diag packet is not 1:1 response (perhaps logging packet?), try primary channel */ if (tty_data == NULL) tty_data = &(diag_tty[0]); spin_lock_irqsave(&diag_tty_lock, flags); if (dbg_ftm_flag == 1) { cmd_code = (int)(*(char *)d_req->buf); subsys_id = (int)(*(char *)(d_req->buf+1)); if (cmd_code == ttydiag_dbg_cmd_code && subsys_id == ttydiag_dbg_subsys_id && dbg_tty_minor != -1) { /* respond to last tty */ ttydiag_dbg_cmd_code = 0; ttydiag_dbg_subsys_id = 0; tty_data = &(diag_tty[dbg_tty_minor]); dbg_tty_minor = -1; } else { tty_data = &(diag_tty[2]); } } if (tty_data->tty == NULL) { spin_unlock_irqrestore(&diag_tty_lock, flags); return -EIO; } tty_allocated = tty_prepare_flip_string(&tty_data->port, &tty_buf, d_req->length); if (tty_allocated < d_req->length) { spin_unlock_irqrestore(&diag_tty_lock, flags); return -ENOMEM; } /* Unset active tty for next request diag tool */ diag_ch->priv_usb = NULL; memcpy(tty_buf, d_req->buf, d_req->length); tty_flip_buffer_push(&tty_data->port); d_req->actual = d_req->length; spin_unlock_irqrestore(&diag_tty_lock, flags); diag_ch->notify(diag_ch->priv, USB_DIAG_WRITE_DONE, d_req); return 0; }
static void smd_tty_read(unsigned long param) { unsigned char *ptr; int avail; struct smd_tty_info *info = (struct smd_tty_info *)param; struct tty_struct *tty = tty_port_tty_get(&info->port); unsigned long flags; if (!tty) return; for (;;) { if (is_in_reset(info)) { /* signal TTY clients using TTY_BREAK */ tty_insert_flip_char(tty, 0x00, TTY_BREAK); tty_flip_buffer_push(tty); break; } if (test_bit(TTY_THROTTLED, &tty->flags)) break; spin_lock_irqsave(&info->ra_lock, flags); avail = smd_read_avail(info->ch); if (avail == 0) { wake_unlock(&info->ra_wake_lock); spin_unlock_irqrestore(&info->ra_lock, flags); break; } spin_unlock_irqrestore(&info->ra_lock, flags); if (avail > MAX_TTY_BUF_SIZE) avail = MAX_TTY_BUF_SIZE; avail = tty_prepare_flip_string(tty, &ptr, avail); if (avail <= 0) { mod_timer(&info->buf_req_timer, jiffies + msecs_to_jiffies(30)); tty_kref_put(tty); return; } if (smd_read(info->ch, ptr, avail) != avail) { /* shouldn't be possible since we're in interrupt ** context here and nobody else could 'steal' our ** characters. */ SMD_TTY_ERR( "%s - Possible smd_tty_buffer mismatch for %s", __func__, info->ch->name); } wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } /* XXX only when writable and necessary */ tty_wakeup(tty); tty_kref_put(tty); }
static void smd_tty_read(unsigned long param) { unsigned char *ptr; int avail; struct smd_tty_info *info = (struct smd_tty_info *)param; struct tty_struct *tty = info->tty; if (!tty) return; for (;;) { unsigned int n = info->tty->index; if (is_in_reset(info)) { if (n == BT_ACL_IDX || n == BT_CMD_IDX) pr_err("%s: BT_IDX read in reset %d \n", __func__, n); if ((n != BT_ACL_IDX) && (n != BT_CMD_IDX)) { /* signal TTY clients using TTY_BREAK */ tty_insert_flip_char(tty, 0x00, TTY_BREAK); tty_flip_buffer_push(tty); break; } } if (test_bit(TTY_THROTTLED, &tty->flags)) break; avail = smd_read_avail(info->ch); if (avail == 0) break; if (avail > MAX_TTY_BUF_SIZE) avail = MAX_TTY_BUF_SIZE; avail = tty_prepare_flip_string(tty, &ptr, avail); if (avail <= 0) { mod_timer(&info->buf_req_timer, jiffies + msecs_to_jiffies(30)); return; } if (smd_read(info->ch, ptr, avail) != avail) { /* shouldn't be possible since we're in interrupt ** context here and nobody else could 'steal' our ** characters. */ printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!"); } #ifdef CONFIG_HAS_WAKELOCK pr_debug("%s: lock wakelock %s\n", __func__, info->wake_lock.name); #endif wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } /* XXX only when writable and necessary */ tty_wakeup(tty); }
static void smd_tty_read(unsigned long param) { unsigned char *ptr; int avail; struct smd_tty_info *info = (struct smd_tty_info *)param; struct tty_struct *tty = info->tty; if (!tty) return; for (;;) { if (is_in_reset(info)) { /* signal TTY clients using TTY_BREAK */ tty_insert_flip_char(tty, 0x00, TTY_BREAK); tty_flip_buffer_push(tty); break; } if (test_bit(TTY_THROTTLED, &tty->flags)) break; avail = smd_read_avail(info->ch); if (avail == 0) break; if (avail > MAX_TTY_BUF_SIZE) avail = MAX_TTY_BUF_SIZE; avail = tty_prepare_flip_string(tty, &ptr, avail); if (avail <= 0) { if (!timer_pending(&info->buf_req_timer)) { init_timer(&info->buf_req_timer); info->buf_req_timer.expires = jiffies + ((30 * HZ)/1000); info->buf_req_timer.function = buf_req_retry; info->buf_req_timer.data = param; add_timer(&info->buf_req_timer); } return; } if (smd_read(info->ch, ptr, avail) != avail) { /* shouldn't be possible since we're in interrupt ** context here and nobody else could 'steal' our ** characters. */ printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!"); } wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } /* XXX only when writable and necessary */ tty_wakeup(tty); }
static void smd_tty_read(unsigned long param) { unsigned char *ptr; int avail; struct smd_tty_info *info = (struct smd_tty_info *)param; struct tty_struct *tty = info->tty; if (!tty) return; for (;;) { if (is_in_reset(info)) { tty_insert_flip_char(tty, 0x00, TTY_BREAK); tty_flip_buffer_push(tty); break; } if (test_bit(TTY_THROTTLED, &tty->flags)) break; avail = smd_read_avail(info->ch); if (avail == 0) break; if (avail > MAX_TTY_BUF_SIZE) avail = MAX_TTY_BUF_SIZE; avail = tty_prepare_flip_string(tty, &ptr, avail); if (avail <= 0) { mod_timer(&info->buf_req_timer, jiffies + msecs_to_jiffies(30)); return; } if (smd_read(info->ch, ptr, avail) != avail) { printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!"); } else { if (get_radio_flag() & 0x0008) { int i = 0; printk("[RIL]"); for (i = 0; i< avail; i++) printk("%c", *(ptr+i)); } } wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } tty_wakeup(tty); }
/** Timer function. */ void ec_tty_wakeup(unsigned long data) { ec_tty_t *tty = (ec_tty_t *) data; size_t to_recv; /* Wake up any process waiting to send data */ if (tty->wakeup) { if (tty->tty) { #if EC_TTY_DEBUG >= 1 printk(KERN_INFO PFX "Waking up.\n"); #endif tty_wakeup(tty->tty); } tty->wakeup = 0; } /* Push received data into TTY core. */ to_recv = ec_tty_rx_size(tty); if (to_recv && tty->tty) { unsigned char *cbuf; int space = tty_prepare_flip_string(tty->tty, &cbuf, to_recv); if (space < to_recv) { printk(KERN_WARNING PFX "Insufficient space to_recv=%d space=%d\n", to_recv, space); } if (space < 0) { to_recv = 0; } else { to_recv = space; } if (to_recv) { unsigned int i; #if EC_TTY_DEBUG >= 1 printk(KERN_INFO PFX "Pushing %u bytes to TTY core.\n", to_recv); #endif for (i = 0; i < to_recv; i++) { cbuf[i] = tty->rx_buffer[tty->rx_read_idx]; tty->rx_read_idx = (tty->rx_read_idx + 1) % EC_TTY_RX_BUFFER_SIZE; } tty_flip_buffer_push(tty->tty); } } tty->timer.expires += 1; add_timer(&tty->timer); }
static void aircable_read(struct work_struct *work) { struct aircable_private *priv = container_of(work, struct aircable_private, rx_work); struct usb_serial_port *port = priv->port; struct tty_struct *tty; unsigned char *data; int count; if (priv->rx_flags & THROTTLED) { if (priv->rx_flags & ACTUALLY_THROTTLED) schedule_work(&priv->rx_work); return; } /* By now I will flush data to the tty in packages of no more than * 64 bytes, to ensure I do not get throttled. * Ask USB mailing list for better aproach. */ tty = tty_port_tty_get(&port->port); if (!tty) { schedule_work(&priv->rx_work); dev_err(&port->dev, "%s - No tty available\n", __func__); return ; } count = min(64, serial_buf_data_avail(priv->rx_buf)); if (count <= 0) goto out; /* We have finished sending everything. */ tty_prepare_flip_string(tty, &data, count); if (!data) { dev_err(&port->dev, "%s- kzalloc(%d) failed.", __func__, count); goto out; } serial_buf_get(priv->rx_buf, data, count); tty_flip_buffer_push(tty); if (serial_buf_data_avail(priv->rx_buf)) schedule_work(&priv->rx_work); out: tty_kref_put(tty); return; }
static int gdun_rx_string(struct sk_buff *skb) { int avail = 0; if (gdun_tty != NULL) { unsigned char *ptr; avail = tty_prepare_flip_string(gdun_tty, &ptr, skb->len); if (avail <= 0) { pr_err("tty_prepare_flip_string err\n"); } else { memcpy(ptr, skb->data, avail); tty_flip_buffer_push(gdun_tty); } } return avail; }
static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id) { struct goldfish_tty *qtty = dev_id; void __iomem *base = qtty->base; unsigned long address; unsigned char *buf; u32 count; count = readl(base + GOLDFISH_TTY_REG_BYTES_READY); if (count == 0) return IRQ_NONE; count = tty_prepare_flip_string(&qtty->port, &buf, count); address = (unsigned long)(void *)buf; goldfish_tty_rw(qtty, address, count, 0); tty_schedule_flip(&qtty->port); return IRQ_HANDLED; }
static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct goldfish_tty *qtty = &goldfish_ttys[pdev->id]; uint32_t base = qtty->base; unsigned long irq_flags; unsigned char *buf; uint32_t count; count = readl(base + GOLDFISH_TTY_BYTES_READY); if(count == 0) { return IRQ_NONE; } count = tty_prepare_flip_string(qtty->tty, &buf, count); spin_lock_irqsave(&qtty->lock, irq_flags); writel(buf, base + GOLDFISH_TTY_DATA_PTR); writel(count, base + GOLDFISH_TTY_DATA_LEN); writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_CMD); spin_unlock_irqrestore(&qtty->lock, irq_flags); tty_schedule_flip(qtty->tty); return IRQ_HANDLED; }
/* ** Routine for handling received data for tty drivers */ static void RIOReceive(struct rio_info *p, struct Port *PortP) { struct tty_struct *TtyP; unsigned short transCount; struct PKT __iomem *PacketP; register unsigned int DataCnt; unsigned char __iomem *ptr; unsigned char *buf; int copied = 0; static int intCount, RxIntCnt; /* ** The receive data process is to remove packets from the ** PHB until there aren't any more or the current cblock ** is full. When this occurs, there will be some left over ** data in the packet, that we must do something with. ** As we haven't unhooked the packet from the read list ** yet, we can just leave the packet there, having first ** made a note of how far we got. This means that we need ** a pointer per port saying where we start taking the ** data from - this will normally be zero, but when we ** run out of space it will be set to the offset of the ** next byte to copy from the packet data area. The packet ** length field is decremented by the number of bytes that ** we successfully removed from the packet. When this reaches ** zero, we reset the offset pointer to be zero, and free ** the packet from the front of the queue. */ intCount++; TtyP = PortP->gs.tty; if (!TtyP) { rio_dprintk(RIO_DEBUG_INTR, "RIOReceive: tty is null. \n"); return; } if (PortP->State & RIO_THROTTLE_RX) { rio_dprintk(RIO_DEBUG_INTR, "RIOReceive: Throttled. Can't handle more input.\n"); return; } if (PortP->State & RIO_DELETED) { while (can_remove_receive(&PacketP, PortP)) { remove_receive(PortP); put_free_end(PortP->HostP, PacketP); } } else { /* ** loop, just so long as: ** i ) there's some data ( i.e. can_remove_receive ) ** ii ) we haven't been blocked ** iii ) there's somewhere to put the data ** iv ) we haven't outstayed our welcome */ transCount = 1; while (can_remove_receive(&PacketP, PortP) && transCount) { RxIntCnt++; /* ** check that it is not a command! */ if (readb(&PacketP->len) & PKT_CMD_BIT) { rio_dprintk(RIO_DEBUG_INTR, "RIO: unexpected command packet received on PHB\n"); /* rio_dprint(RIO_DEBUG_INTR, (" sysport = %d\n", p->RIOPortp->PortNum)); */ rio_dprintk(RIO_DEBUG_INTR, " dest_unit = %d\n", readb(&PacketP->dest_unit)); rio_dprintk(RIO_DEBUG_INTR, " dest_port = %d\n", readb(&PacketP->dest_port)); rio_dprintk(RIO_DEBUG_INTR, " src_unit = %d\n", readb(&PacketP->src_unit)); rio_dprintk(RIO_DEBUG_INTR, " src_port = %d\n", readb(&PacketP->src_port)); rio_dprintk(RIO_DEBUG_INTR, " len = %d\n", readb(&PacketP->len)); rio_dprintk(RIO_DEBUG_INTR, " control = %d\n", readb(&PacketP->control)); rio_dprintk(RIO_DEBUG_INTR, " csum = %d\n", readw(&PacketP->csum)); rio_dprintk(RIO_DEBUG_INTR, " data bytes: "); for (DataCnt = 0; DataCnt < PKT_MAX_DATA_LEN; DataCnt++) rio_dprintk(RIO_DEBUG_INTR, "%d\n", readb(&PacketP->data[DataCnt])); remove_receive(PortP); put_free_end(PortP->HostP, PacketP); continue; /* with next packet */ } /* ** How many characters can we move 'upstream' ? ** ** Determine the minimum of the amount of data ** available and the amount of space in which to ** put it. ** ** 1. Get the packet length by masking 'len' ** for only the length bits. ** 2. Available space is [buffer size] - [space used] ** ** Transfer count is the minimum of packet length ** and available space. */ transCount = tty_buffer_request_room(TtyP, readb(&PacketP->len) & PKT_LEN_MASK); rio_dprintk(RIO_DEBUG_REC, "port %d: Copy %d bytes\n", PortP->PortNum, transCount); /* ** To use the following 'kkprintfs' for debugging - change the '#undef' ** to '#define', (this is the only place ___DEBUG_IT___ occurs in the ** driver). */ ptr = (unsigned char __iomem *) PacketP->data + PortP->RxDataStart; tty_prepare_flip_string(TtyP, &buf, transCount); rio_memcpy_fromio(buf, ptr, transCount); PortP->RxDataStart += transCount; writeb(readb(&PacketP->len)-transCount, &PacketP->len); copied += transCount; if (readb(&PacketP->len) == 0) { /* ** If we have emptied the packet, then we can ** free it, and reset the start pointer for ** the next packet. */ remove_receive(PortP); put_free_end(PortP->HostP, PacketP); PortP->RxDataStart = 0; } } } if (copied) { rio_dprintk(RIO_DEBUG_REC, "port %d: pushing tty flip buffer: %d total bytes copied.\n", PortP->PortNum, copied); tty_flip_buffer_push(TtyP); } return; }
static void atcmd_tty_read(struct work_struct *work) { struct atcmd_tty_info *info = container_of(work, struct atcmd_tty_info, work.work); struct buf_fifo *read_buffer; struct tty_struct *tty = info->tty; unsigned char *ptr; int n_read; int avail, remain, buf_size; char *read_addr; read_buffer = info->read_buffer; mutex_lock(read_buffer->lock); read_addr = read_buffer->buf_addr + read_buffer->tail; buf_size = read_buffer->size; if (!tty) { mutex_unlock(read_buffer->lock); return; } for (;;) { if (test_bit(TTY_THROTTLED, &tty->flags)) break; n_read = buf_size - get_free_space(read_buffer); if (n_read == 0) break; avail = tty_prepare_flip_string(tty, &ptr, n_read); if (avail <= 0) { schedule_delayed_work(&info->work, (30 / 1000) * HZ); mutex_unlock(read_buffer->lock); return; } if (read_buffer->head > read_buffer->tail) { memcpy(ptr, read_addr, avail); read_buffer->tail += avail; } else { int read_len = avail; remain = buf_size - read_buffer->tail; if (remain < avail) { memcpy(ptr, read_addr, remain); ptr += remain; read_len -= remain; read_buffer->tail = 0; read_addr = read_buffer->buf_addr; } memcpy(ptr, read_addr, read_len); read_buffer->tail += read_len; read_buffer->tail %= buf_size; } read_buffer->count -= avail; wake_lock_timeout(&info->wake_lock, HZ / 2); tty_flip_buffer_push(tty); } mutex_unlock(read_buffer->lock); if (info->other_tty->tty) tty_wakeup(info->other_tty->tty); return; }
static void smd_tty_notify(void *priv, unsigned event) { struct smd_tty_info *info = priv; unsigned long flags; unsigned char *ptr; switch (event) { case SMD_EVENT_DATA: spin_lock_irqsave(&info->reset_lock, flags); if (!info->is_open) { spin_unlock_irqrestore(&info->reset_lock, flags); break; } spin_unlock_irqrestore(&info->reset_lock, flags); /* There may be clients (tty framework) that are blocked * waiting for space to write data, so if a possible read * interrupt came in wake anyone waiting and disable the * interrupts */ if (smd_write_avail(info->ch)) { smd_disable_read_intr(info->ch); if (info->tty) { unsigned int n = info->tty->index; wake_up_interruptible(&info->tty->write_wait); /* use pm_qos for BT performance */ if (n == BT_ACL_IDX || n == BT_CMD_IDX) schedule_work(&pm_qos_set_work); } } tasklet_hi_schedule(&info->tty_tsklt); break; case SMD_EVENT_OPEN: if (is_in_reset(info)) { unsigned int n = info->tty->index; if (n == BT_CMD_IDX) { pr_err("%s: BT_CMD_IDX Sending hardware error event to stack\n", __func__); tty_prepare_flip_string(info->tty, &ptr, 0x03); ptr[0] = 0x10; ptr[1] = 0x01; ptr[2] = 0x0A; tty_flip_buffer_push(info->tty); } } spin_lock_irqsave(&info->reset_lock, flags); info->in_reset = 0; info->in_reset_updated = 1; info->is_open = 1; wake_up_interruptible(&info->ch_opened_wait_queue); spin_unlock_irqrestore(&info->reset_lock, flags); break; case SMD_EVENT_CLOSE: spin_lock_irqsave(&info->reset_lock, flags); info->in_reset = 1; info->in_reset_updated = 1; info->is_open = 0; wake_up_interruptible(&info->ch_opened_wait_queue); spin_unlock_irqrestore(&info->reset_lock, flags); /* schedule task to send TTY_BREAK */ tasklet_hi_schedule(&info->tty_tsklt); if (info->tty->index == LOOPBACK_IDX) schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000)); break; } }
int atcmd_write_toatd(struct gdata_port *port, struct sk_buff *skb) { struct tty_struct *tty; unsigned char *ptr; int avail; char *cmd; int i; pr_debug("%s\n", __func__); tty = port->tty; if (!tty) return -ENODEV; avail = skb->len; if (avail == 0) return -EINVAL; ptr = skb->data + avail - 1; if (strncasecmp(skb->data, "AT", 2) || !(*ptr == '\r' || *ptr == '\n' || *ptr == '\0')) { return -EINVAL; } cmd = kstrdup(skb->data + 2, GFP_ATOMIC); if (!cmd) { pr_debug("%s: ENOMEM\n", __func__); return -ENOMEM; } if ((ptr = strchr(cmd, '=')) || (ptr = strchr(cmd, '?')) || (ptr = strchr(cmd, '\r')) ) { *ptr = '\0'; } if (*cmd != '\0') { for (i = 0; at_table[i] != NULL; i++) { if (!strcasecmp(cmd, at_table[i])) { kfree(cmd); if (!test_bit(CH_OPENED, &port->bridge_sts)) { /* signal TTY clients using TTY_BREAK */ tty_insert_flip_char(tty, 0x00, TTY_BREAK); tty_flip_buffer_push(tty); break; } else { avail = tty_prepare_flip_string(tty, &ptr, avail); if (avail <= 0) { return -EBUSY; } #ifdef VERBOSE_DEBUG print_hex_dump(KERN_DEBUG, "toatd:", DUMP_PREFIX_OFFSET, 16, 1, skb->data, skb->len, 1); #endif memcpy(ptr, skb->data, avail); dev_kfree_skb_any(skb); tty_flip_buffer_push(tty); } /* XXX only when writable and necessary */ tty_wakeup(tty); return 0; } } } kfree(cmd); return -ENOENT; }