/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ /* * SDIO DMUX module. */ #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #define SDIO_CH_LOCAL_OPEN 0x1 #define SDIO_CH_REMOTE_OPEN 0x2 #define SDIO_CH_IN_RESET 0x4 #define SDIO_MUX_HDR_MAGIC_NO 0x33fc #define SDIO_MUX_HDR_CMD_DATA 0 #define SDIO_MUX_HDR_CMD_OPEN 1 #define SDIO_MUX_HDR_CMD_CLOSE 2 #define LOW_WATERMARK 2 #define HIGH_WATERMARK 4 static int msm_sdio_dmux_debug_enable; module_param_named(debug_enable, msm_sdio_dmux_debug_enable, int, S_IRUGO | S_IWUSR | S_IWGRP); #if defined(DEBUG) static uint32_t sdio_dmux_read_cnt; static uint32_t sdio_dmux_write_cnt; static uint32_t sdio_dmux_write_cpy_cnt; static uint32_t sdio_dmux_write_cpy_bytes; #define DBG(x...) do { \ if (msm_sdio_dmux_debug_enable) \ pr_debug(x); \ } while (0) #define DBG_INC_READ_CNT(x) do { \ sdio_dmux_read_cnt += (x); \ if (msm_sdio_dmux_debug_enable) \ pr_debug("%s: total read bytes %u\n", \ __func__, sdio_dmux_read_cnt); \ } while (0) #define DBG_INC_WRITE_CNT(x) do { \ sdio_dmux_write_cnt += (x); \ if (msm_sdio_dmux_debug_enable) \ pr_debug("%s: total written bytes %u\n", \ __func__, sdio_dmux_write_cnt); \ } while (0) #define DBG_INC_WRITE_CPY(x) do { \ sdio_dmux_write_cpy_bytes += (x); \ sdio_dmux_write_cpy_cnt++; \ if (msm_sdio_dmux_debug_enable) \ pr_debug("%s: total write copy cnt %u, bytes %u\n", \ __func__, sdio_dmux_write_cpy_cnt, \ sdio_dmux_write_cpy_bytes); \ } while (0) #else #define DBG(x...) do { } while (0) #define DBG_INC_READ_CNT(x...) do { } while (0) #define DBG_INC_WRITE_CNT(x...) do { } while (0) #define DBG_INC_WRITE_CPY(x...) do { } while (0) #endif struct sdio_ch_info { uint32_t status; void (*receive_cb)(void *, struct sk_buff *); void (*write_done)(void *, struct sk_buff *); void *priv; spinlock_t lock; int num_tx_pkts; int use_wm; }; static struct sk_buff_head sdio_mux_write_pool; static spinlock_t sdio_mux_write_lock; static struct sdio_channel *sdio_mux_ch; static struct sdio_ch_info sdio_ch[SDIO_DMUX_NUM_CHANNELS]; struct wake_lock sdio_mux_ch_wakelock; static int sdio_mux_initialized; static int fatal_error; struct sdio_mux_hdr { uint16_t magic_num; uint8_t reserved; uint8_t cmd; uint8_t pad_len; uint8_t ch_id; uint16_t pkt_len; }; struct sdio_partial_pkt_info { uint32_t valid; struct sk_buff *skb; struct sdio_mux_hdr *hdr; }; static void sdio_mux_read_data(struct work_struct *work); static void sdio_mux_write_data(struct work_struct *work); static void sdio_mux_send_open_cmd(uint32_t id); static DEFINE_MUTEX(sdio_mux_lock); static DECLARE_WORK(work_sdio_mux_read, sdio_mux_read_data); static DECLARE_WORK(work_sdio_mux_write, sdio_mux_write_data); static DECLARE_DELAYED_WORK(delayed_work_sdio_mux_write, sdio_mux_write_data); static struct workqueue_struct *sdio_mux_workqueue; static struct sdio_partial_pkt_info sdio_partial_pkt; #define sdio_ch_is_open(x) \ (sdio_ch[(x)].status == (SDIO_CH_LOCAL_OPEN | SDIO_CH_REMOTE_OPEN)) #define sdio_ch_is_local_open(x) \ (sdio_ch[(x)].status & SDIO_CH_LOCAL_OPEN) #define sdio_ch_is_remote_open(x) \ (sdio_ch[(x)].status & SDIO_CH_REMOTE_OPEN) #define sdio_ch_is_in_reset(x) \ (sdio_ch[(x)].status & SDIO_CH_IN_RESET) static inline void skb_set_data(struct sk_buff *skb, unsigned char *data, unsigned int len) { /* panic if tail > end */ skb->data = data; skb->tail = skb->data + len; skb->len = len; skb->truesize = len + sizeof(struct sk_buff); } static void sdio_mux_save_partial_pkt(struct sdio_mux_hdr *hdr, struct sk_buff *skb_mux) { struct sk_buff *skb; /* i think we can avoid cloning here */ skb = skb_clone(skb_mux, GFP_KERNEL); if (!skb) { pr_err("%s: cannot clone skb\n", __func__); return; } /* protect? */ skb_set_data(skb, (unsigned char *)hdr, skb->tail - (unsigned char *)hdr); sdio_partial_pkt.skb = skb; sdio_partial_pkt.valid = 1; DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, skb->head, skb->data, skb->tail, skb->end, skb->len); return; } static void *handle_sdio_mux_data(struct sdio_mux_hdr *hdr, struct sk_buff *skb_mux) { struct sk_buff *skb; void *rp = (void *)hdr; unsigned long flags; /* protect? */ rp += sizeof(*hdr); if (rp < (void *)skb_mux->tail) rp += (hdr->pkt_len + hdr->pad_len); if (rp > (void *)skb_mux->tail) { /* partial packet */ sdio_mux_save_partial_pkt(hdr, skb_mux); goto packet_done; } DBG("%s: hdr %p next %p tail %p pkt_size %d\n", __func__, hdr, rp, skb_mux->tail, hdr->pkt_len + hdr->pad_len); skb = skb_clone(skb_mux, GFP_KERNEL); if (!skb) { pr_err("%s: cannot clone skb\n", __func__); goto packet_done; } skb_set_data(skb, (unsigned char *)(hdr + 1), hdr->pkt_len); DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, skb->head, skb->data, skb->tail, skb->end, skb->len); /* probably we should check channel status */ /* discard packet early if local side not open */ spin_lock_irqsave(&sdio_ch[hdr->ch_id].lock, flags); if (sdio_ch[hdr->ch_id].receive_cb) sdio_ch[hdr->ch_id].receive_cb(sdio_ch[hdr->ch_id].priv, skb); else dev_kfree_skb_any(skb); spin_unlock_irqrestore(&sdio_ch[hdr->ch_id].lock, flags); packet_done: return rp; } static void *handle_sdio_mux_command(struct sdio_mux_hdr *hdr, struct sk_buff *skb_mux) { void *rp; unsigned long flags; int send_open = 0; DBG("%s: cmd %d ch %d\n", __func__, hdr->cmd, hdr->ch_id); switch (hdr->cmd) { case SDIO_MUX_HDR_CMD_DATA: rp = handle_sdio_mux_data(hdr, skb_mux); break; case SDIO_MUX_HDR_CMD_OPEN: spin_lock_irqsave(&sdio_ch[hdr->ch_id].lock, flags); sdio_ch[hdr->ch_id].status |= SDIO_CH_REMOTE_OPEN; sdio_ch[hdr->ch_id].num_tx_pkts = 0; if (sdio_ch_is_in_reset(hdr->ch_id)) { DBG("%s: in reset - sending open cmd\n", __func__); sdio_ch[hdr->ch_id].status &= ~SDIO_CH_IN_RESET; send_open = 1; } /* notify client so it can update its status */ if (sdio_ch[hdr->ch_id].receive_cb) sdio_ch[hdr->ch_id].receive_cb( sdio_ch[hdr->ch_id].priv, NULL); if (sdio_ch[hdr->ch_id].write_done) sdio_ch[hdr->ch_id].write_done( sdio_ch[hdr->ch_id].priv, NULL); spin_unlock_irqrestore(&sdio_ch[hdr->ch_id].lock, flags); rp = hdr + 1; if (send_open) sdio_mux_send_open_cmd(hdr->ch_id); break; case SDIO_MUX_HDR_CMD_CLOSE: /* probably should drop pending write */ spin_lock_irqsave(&sdio_ch[hdr->ch_id].lock, flags); sdio_ch[hdr->ch_id].status &= ~SDIO_CH_REMOTE_OPEN; spin_unlock_irqrestore(&sdio_ch[hdr->ch_id].lock, flags); rp = hdr + 1; break; default: rp = hdr + 1; } return rp; } static void *handle_sdio_partial_pkt(struct sk_buff *skb_mux) { struct sk_buff *p_skb; struct sdio_mux_hdr *p_hdr; void *ptr, *rp = skb_mux->data; /* protoect? */ if (sdio_partial_pkt.valid) { p_skb = sdio_partial_pkt.skb; ptr = skb_push(skb_mux, p_skb->len); memcpy(ptr, p_skb->data, p_skb->len); sdio_partial_pkt.skb = NULL; sdio_partial_pkt.valid = 0; dev_kfree_skb_any(p_skb); DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, skb_mux->head, skb_mux->data, skb_mux->tail, skb_mux->end, skb_mux->len); p_hdr = (struct sdio_mux_hdr *)skb_mux->data; rp = handle_sdio_mux_command(p_hdr, skb_mux); } return rp; } static void sdio_mux_read_data(struct work_struct *work) { struct sk_buff *skb_mux; void *ptr = 0; int sz, rc, len = 0; struct sdio_mux_hdr *hdr; static int workqueue_pinned; if (!workqueue_pinned) { struct cpumask cpus; cpumask_clear(&cpus); cpumask_set_cpu(0, &cpus); if (sched_setaffinity(current->pid, &cpus)) pr_err("%s: sdio_dmux set CPU affinity failed\n", __func__); workqueue_pinned = 1; } DBG("%s: reading\n", __func__); /* should probably have a separate read lock */ mutex_lock(&sdio_mux_lock); sz = sdio_read_avail(sdio_mux_ch); DBG("%s: read avail %d\n", __func__, sz); if (sz <= 0) { if (sz) pr_err("%s: read avail failed %d\n", __func__, sz); mutex_unlock(&sdio_mux_lock); return; } /* net_ip_aling is probably not required */ if (sdio_partial_pkt.valid) len = sdio_partial_pkt.skb->len; /* If allocation fails attempt to get a smaller chunk of mem */ do { skb_mux = __dev_alloc_skb(sz + NET_IP_ALIGN + len, GFP_KERNEL); if (skb_mux) break; pr_err("%s: cannot allocate skb of size:%d + " "%d (NET_SKB_PAD)\n", __func__, sz + NET_IP_ALIGN + len, NET_SKB_PAD); /* the skb structure adds NET_SKB_PAD bytes to the memory * request, which may push the actual request above PAGE_SIZE * in that case, we need to iterate one more time to make sure * we get the memory request under PAGE_SIZE */ if (sz + NET_IP_ALIGN + len + NET_SKB_PAD <= PAGE_SIZE) { pr_err("%s: allocation failed\n", __func__); mutex_unlock(&sdio_mux_lock); return; } sz /= 2; } while (1); skb_reserve(skb_mux, NET_IP_ALIGN + len); ptr = skb_put(skb_mux, sz); /* half second wakelock is fine? */ wake_lock_timeout(&sdio_mux_ch_wakelock, HZ / 2); rc = sdio_read(sdio_mux_ch, ptr, sz); DBG("%s: read %d\n", __func__, rc); if (rc) { pr_err("%s: sdio read failed %d\n", __func__, rc); dev_kfree_skb_any(skb_mux); mutex_unlock(&sdio_mux_lock); queue_work(sdio_mux_workqueue, &work_sdio_mux_read); return; } mutex_unlock(&sdio_mux_lock); DBG_INC_READ_CNT(sz); DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, skb_mux->head, skb_mux->data, skb_mux->tail, skb_mux->end, skb_mux->len); /* move to a separate function */ /* probably do skb_pull instead of pointer adjustment */ hdr = handle_sdio_partial_pkt(skb_mux); while ((void *)hdr < (void *)skb_mux->tail) { if (((void *)hdr + sizeof(*hdr)) > (void *)skb_mux->tail) { /* handle partial header */ sdio_mux_save_partial_pkt(hdr, skb_mux); break; } if (hdr->magic_num != SDIO_MUX_HDR_MAGIC_NO) { pr_err("%s: packet error\n", __func__); break; } hdr = handle_sdio_mux_command(hdr, skb_mux); } dev_kfree_skb_any(skb_mux); DBG("%s: read done\n", __func__); queue_work(sdio_mux_workqueue, &work_sdio_mux_read); } static int sdio_mux_write(struct sk_buff *skb) { int rc, sz; mutex_lock(&sdio_mux_lock); sz = sdio_write_avail(sdio_mux_ch); DBG("%s: avail %d len %d\n", __func__, sz, skb->len); if (skb->len <= sz) { rc = sdio_write(sdio_mux_ch, skb->data, skb->len); DBG("%s: write returned %d\n", __func__, rc); if (rc == 0) DBG_INC_WRITE_CNT(skb->len); } else rc = -ENOMEM; mutex_unlock(&sdio_mux_lock); return rc; } static int sdio_mux_write_cmd(void *data, uint32_t len) { int avail, rc; for (;;) { mutex_lock(&sdio_mux_lock); avail = sdio_write_avail(sdio_mux_ch); DBG("%s: avail %d len %d\n", __func__, avail, len); if (avail >= len) { rc = sdio_write(sdio_mux_ch, data, len); DBG("%s: write returned %d\n", __func__, rc); if (!rc) { DBG_INC_WRITE_CNT(len); break; } } mutex_unlock(&sdio_mux_lock); msleep(250); } mutex_unlock(&sdio_mux_lock); return 0; } static void sdio_mux_send_open_cmd(uint32_t id) { struct sdio_mux_hdr hdr = { .magic_num = SDIO_MUX_HDR_MAGIC_NO, .cmd = SDIO_MUX_HDR_CMD_OPEN, .reserved = 0, .ch_id = id, .pkt_len = 0, .pad_len = 0 }; sdio_mux_write_cmd((void *)&hdr, sizeof(hdr)); } static void sdio_mux_write_data(struct work_struct *work) { int rc, reschedule = 0; int notify = 0; struct sk_buff *skb; unsigned long flags; int avail; int ch_id; spin_lock_irqsave(&sdio_mux_write_lock, flags); while ((skb = __skb_dequeue(&sdio_mux_write_pool))) { ch_id = ((struct sdio_mux_hdr *)skb->data)->ch_id; avail = sdio_write_avail(sdio_mux_ch); if (avail < skb->len) { /* we may have to wait for write avail * notification from sdio al */ DBG("%s: sdio_write_avail(%d) < skb->len(%d)\n", __func__, avail, skb->len); reschedule = 1; break; } spin_unlock_irqrestore(&sdio_mux_write_lock, flags); rc = sdio_mux_write(skb); spin_lock_irqsave(&sdio_mux_write_lock, flags); if (rc == 0) { spin_lock(&sdio_ch[ch_id].lock); sdio_ch[ch_id].num_tx_pkts--; spin_unlock(&sdio_ch[ch_id].lock); if (sdio_ch[ch_id].write_done) sdio_ch[ch_id].write_done( sdio_ch[ch_id].priv, skb); else dev_kfree_skb_any(skb); } else if (rc == -EAGAIN || rc == -ENOMEM) { /* recoverable error - retry again later */ reschedule = 1; break; } else if (rc == -ENODEV) { /* * sdio_al suffered some kind of fatal error * prevent future writes and clean up pending ones */ fatal_error = 1; do { ch_id = ((struct sdio_mux_hdr *) skb->data)->ch_id; spin_lock(&sdio_ch[ch_id].lock); sdio_ch[ch_id].num_tx_pkts--; spin_unlock(&sdio_ch[ch_id].lock); dev_kfree_skb_any(skb); } while ((skb = __skb_dequeue(&sdio_mux_write_pool))); spin_unlock_irqrestore(&sdio_mux_write_lock, flags); return; } else { /* unknown error condition - drop the * skb and reschedule for the * other skb's */ pr_err("%s: sdio_mux_write error %d" " for ch %d, skb=%p\n", __func__, rc, ch_id, skb); notify = 1; break; } } if (reschedule) { if (sdio_ch_is_in_reset(ch_id)) { notify = 1; } else { __skb_queue_head(&sdio_mux_write_pool, skb); queue_delayed_work(sdio_mux_workqueue, &delayed_work_sdio_mux_write, msecs_to_jiffies(250) ); } } if (notify) { spin_lock(&sdio_ch[ch_id].lock); sdio_ch[ch_id].num_tx_pkts--; spin_unlock(&sdio_ch[ch_id].lock); if (sdio_ch[ch_id].write_done) sdio_ch[ch_id].write_done( sdio_ch[ch_id].priv, skb); else dev_kfree_skb_any(skb); } spin_unlock_irqrestore(&sdio_mux_write_lock, flags); } int msm_sdio_is_channel_in_reset(uint32_t id) { int rc = 0; if (id >= SDIO_DMUX_NUM_CHANNELS) return -EINVAL; if (sdio_ch_is_in_reset(id)) rc = 1; return rc; } int msm_sdio_dmux_write(uint32_t id, struct sk_buff *skb) { int rc = 0; struct sdio_mux_hdr *hdr; unsigned long flags; struct sk_buff *new_skb; if (id >= SDIO_DMUX_NUM_CHANNELS) return -EINVAL; if (!skb) return -EINVAL; if (!sdio_mux_initialized) return -ENODEV; if (fatal_error) return -ENODEV; DBG("%s: writing to ch %d len %d\n", __func__, id, skb->len); spin_lock_irqsave(&sdio_ch[id].lock, flags); if (sdio_ch_is_in_reset(id)) { spin_unlock_irqrestore(&sdio_ch[id].lock, flags); pr_err("%s: port is in reset: %d\n", __func__, sdio_ch[id].status); return -ENETRESET; } if (!sdio_ch_is_local_open(id)) { spin_unlock_irqrestore(&sdio_ch[id].lock, flags); pr_err("%s: port not open: %d\n", __func__, sdio_ch[id].status); return -ENODEV; } if (sdio_ch[id].use_wm && (sdio_ch[id].num_tx_pkts >= HIGH_WATERMARK)) { spin_unlock_irqrestore(&sdio_ch[id].lock, flags); pr_err("%s: watermark exceeded: %d\n", __func__, id); return -EAGAIN; } spin_unlock_irqrestore(&sdio_ch[id].lock, flags); spin_lock_irqsave(&sdio_mux_write_lock, flags); /* if skb do not have any tailroom for padding, copy the skb into a new expanded skb */ if ((skb->len & 0x3) && (skb_tailroom(skb) < (4 - (skb->len & 0x3)))) { /* revisit, probably dev_alloc_skb and memcpy is effecient */ new_skb = skb_copy_expand(skb, skb_headroom(skb), 4 - (skb->len & 0x3), GFP_ATOMIC); if (new_skb == NULL) { pr_err("%s: cannot allocate skb\n", __func__); rc = -ENOMEM; goto write_done; } dev_kfree_skb_any(skb); skb = new_skb; DBG_INC_WRITE_CPY(skb->len); } hdr = (struct sdio_mux_hdr *)skb_push(skb, sizeof(struct sdio_mux_hdr)); /* caller should allocate for hdr and padding hdr is fine, padding is tricky */ hdr->magic_num = SDIO_MUX_HDR_MAGIC_NO; hdr->cmd = SDIO_MUX_HDR_CMD_DATA; hdr->reserved = 0; hdr->ch_id = id; hdr->pkt_len = skb->len - sizeof(struct sdio_mux_hdr); if (skb->len & 0x3) skb_put(skb, 4 - (skb->len & 0x3)); hdr->pad_len = skb->len - (sizeof(struct sdio_mux_hdr) + hdr->pkt_len); DBG("%s: data %p, tail %p skb len %d pkt len %d pad len %d\n", __func__, skb->data, skb->tail, skb->len, hdr->pkt_len, hdr->pad_len); __skb_queue_tail(&sdio_mux_write_pool, skb); spin_lock(&sdio_ch[id].lock); sdio_ch[id].num_tx_pkts++; spin_unlock(&sdio_ch[id].lock); queue_work(sdio_mux_workqueue, &work_sdio_mux_write); write_done: spin_unlock_irqrestore(&sdio_mux_write_lock, flags); return rc; } int msm_sdio_dmux_open(uint32_t id, void *priv, void (*receive_cb)(void *, struct sk_buff *), void (*write_done)(void *, struct sk_buff *)) { unsigned long flags; DBG("%s: opening ch %d\n", __func__, id); if (!sdio_mux_initialized) return -ENODEV; if (id >= SDIO_DMUX_NUM_CHANNELS) return -EINVAL; spin_lock_irqsave(&sdio_ch[id].lock, flags); if (sdio_ch_is_local_open(id)) { pr_info("%s: Already opened %d\n", __func__, id); spin_unlock_irqrestore(&sdio_ch[id].lock, flags); goto open_done; } sdio_ch[id].receive_cb = receive_cb; sdio_ch[id].write_done = write_done; sdio_ch[id].priv = priv; sdio_ch[id].status |= SDIO_CH_LOCAL_OPEN; sdio_ch[id].num_tx_pkts = 0; sdio_ch[id].use_wm = 0; spin_unlock_irqrestore(&sdio_ch[id].lock, flags); sdio_mux_send_open_cmd(id); open_done: pr_info("%s: opened ch %d\n", __func__, id); return 0; } int msm_sdio_dmux_close(uint32_t id) { struct sdio_mux_hdr hdr; unsigned long flags; if (id >= SDIO_DMUX_NUM_CHANNELS) return -EINVAL; DBG("%s: closing ch %d\n", __func__, id); if (!sdio_mux_initialized) return -ENODEV; spin_lock_irqsave(&sdio_ch[id].lock, flags); sdio_ch[id].receive_cb = NULL; sdio_ch[id].priv = NULL; sdio_ch[id].status &= ~SDIO_CH_LOCAL_OPEN; sdio_ch[id].status &= ~SDIO_CH_IN_RESET; spin_unlock_irqrestore(&sdio_ch[id].lock, flags); hdr.magic_num = SDIO_MUX_HDR_MAGIC_NO; hdr.cmd = SDIO_MUX_HDR_CMD_CLOSE; hdr.reserved = 0; hdr.ch_id = id; hdr.pkt_len = 0; hdr.pad_len = 0; sdio_mux_write_cmd((void *)&hdr, sizeof(hdr)); pr_info("%s: closed ch %d\n", __func__, id); return 0; } static void sdio_mux_notify(void *_dev, unsigned event) { DBG("%s: event %d notified\n", __func__, event); /* write avail may not be enouogh for a packet, but should be fine */ if ((event == SDIO_EVENT_DATA_WRITE_AVAIL) && sdio_write_avail(sdio_mux_ch)) queue_work(sdio_mux_workqueue, &work_sdio_mux_write); if ((event == SDIO_EVENT_DATA_READ_AVAIL) && sdio_read_avail(sdio_mux_ch)) queue_work(sdio_mux_workqueue, &work_sdio_mux_read); } int msm_sdio_dmux_is_ch_full(uint32_t id) { unsigned long flags; int ret; if (id >= SDIO_DMUX_NUM_CHANNELS) return -EINVAL; spin_lock_irqsave(&sdio_ch[id].lock, flags); sdio_ch[id].use_wm = 1; ret = sdio_ch[id].num_tx_pkts >= HIGH_WATERMARK; DBG("%s: ch %d num tx pkts=%d, HWM=%d\n", __func__, id, sdio_ch[id].num_tx_pkts, ret); if (!sdio_ch_is_local_open(id)) { ret = -ENODEV; pr_err("%s: port not open: %d\n", __func__, sdio_ch[id].status); } spin_unlock_irqrestore(&sdio_ch[id].lock, flags); return ret; } int msm_sdio_dmux_is_ch_low(uint32_t id) { int ret; if (id >= SDIO_DMUX_NUM_CHANNELS) return -EINVAL; sdio_ch[id].use_wm = 1; ret = sdio_ch[id].num_tx_pkts <= LOW_WATERMARK; DBG("%s: ch %d num tx pkts=%d, LWM=%d\n", __func__, id, sdio_ch[id].num_tx_pkts, ret); if (!sdio_ch_is_local_open(id)) { ret = -ENODEV; pr_err("%s: port not open: %d\n", __func__, sdio_ch[id].status); } return ret; } #ifdef CONFIG_DEBUG_FS static int debug_tbl(char *buf, int max) { int i = 0; int j; for (j = 0; j < SDIO_DMUX_NUM_CHANNELS; ++j) { i += scnprintf(buf + i, max - i, "ch%02d local open=%s remote open=%s\n", j, sdio_ch_is_local_open(j) ? "Y" : "N", sdio_ch_is_remote_open(j) ? "Y" : "N"); } return i; } #define DEBUG_BUFMAX 4096 static char debug_buffer[DEBUG_BUFMAX]; static ssize_t debug_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int (*fill)(char *buf, int max) = file->private_data; int bsize = fill(debug_buffer, DEBUG_BUFMAX); return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); } static int debug_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static const struct file_operations debug_ops = { .read = debug_read, .open = debug_open, }; static void debug_create(const char *name, mode_t mode, struct dentry *dent, int (*fill)(char *buf, int max)) { debugfs_create_file(name, mode, dent, fill, &debug_ops); } #endif static int sdio_dmux_probe(struct platform_device *pdev) { int rc; DBG("%s probe called\n", __func__); if (!sdio_mux_initialized) { sdio_mux_workqueue = create_singlethread_workqueue("sdio_dmux"); if (!sdio_mux_workqueue) return -ENOMEM; skb_queue_head_init(&sdio_mux_write_pool); spin_lock_init(&sdio_mux_write_lock); for (rc = 0; rc < SDIO_DMUX_NUM_CHANNELS; ++rc) spin_lock_init(&sdio_ch[rc].lock); wake_lock_init(&sdio_mux_ch_wakelock, WAKE_LOCK_SUSPEND, "sdio_dmux"); } rc = sdio_open("SDIO_RMNT", &sdio_mux_ch, NULL, sdio_mux_notify); if (rc < 0) { pr_err("%s: sido open failed %d\n", __func__, rc); wake_lock_destroy(&sdio_mux_ch_wakelock); destroy_workqueue(sdio_mux_workqueue); sdio_mux_initialized = 0; return rc; } fatal_error = 0; sdio_mux_initialized = 1; return 0; } static int sdio_dmux_remove(struct platform_device *pdev) { int i; unsigned long ch_lock_flags; unsigned long write_lock_flags; struct sk_buff *skb; DBG("%s remove called\n", __func__); if (!sdio_mux_initialized) return 0; /* set reset state for any open channels */ for (i = 0; i < SDIO_DMUX_NUM_CHANNELS; ++i) { spin_lock_irqsave(&sdio_ch[i].lock, ch_lock_flags); if (sdio_ch_is_open(i)) { sdio_ch[i].status |= SDIO_CH_IN_RESET; sdio_ch[i].status &= ~SDIO_CH_REMOTE_OPEN; /* notify client so it can update its status */ if (sdio_ch[i].receive_cb) sdio_ch[i].receive_cb( sdio_ch[i].priv, NULL); } spin_unlock_irqrestore(&sdio_ch[i].lock, ch_lock_flags); } /* cancel any pending writes */ spin_lock_irqsave(&sdio_mux_write_lock, write_lock_flags); while ((skb = __skb_dequeue(&sdio_mux_write_pool))) { i = ((struct sdio_mux_hdr *)skb->data)->ch_id; if (sdio_ch[i].write_done) sdio_ch[i].write_done( sdio_ch[i].priv, skb); else dev_kfree_skb_any(skb); } spin_unlock_irqrestore(&sdio_mux_write_lock, write_lock_flags); return 0; } static struct platform_driver sdio_dmux_driver = { .probe = sdio_dmux_probe, .remove = sdio_dmux_remove, .driver = { .name = "SDIO_RMNT", .owner = THIS_MODULE, }, }; static int __init sdio_dmux_init(void) { #ifdef CONFIG_DEBUG_FS struct dentry *dent; dent = debugfs_create_dir("sdio_dmux", 0); if (!IS_ERR(dent)) debug_create("tbl", 0444, dent, debug_tbl); #endif return platform_driver_register(&sdio_dmux_driver); } module_init(sdio_dmux_init); MODULE_DESCRIPTION("MSM SDIO DMUX"); MODULE_LICENSE("GPL v2");