smd driver of msm

/**********************************************************************/
arch/arm/mach-msm/smd.c

module_init(msm_smd_init);
static struct platform_driver msm_smd_driver = {
    .probe = msm_smd_probe,
    .driver = {
        .name = MODULE_NAME,
        .owner = THIS_MODULE,
    },
};

static int __devinit msm_smd_probe(struct platform_device *pdev)
{
    INIT_WORK(&probe_work, smd_channel_probe_worker);
    smd_core_init();
    do_smd_probe();
    smd_initialized = 1;

    return 0;
}

/**********************************************************************/
/*struct work_struct probe_work*/
Smd.c (arch\arm\mach-msm):static struct work_struct probe_work;
Smd.c (arch\arm\mach-msm):        schedule_work(&probe_work);
Smd.c (arch\arm\mach-msm):    INIT_WORK(&probe_work, smd_channel_probe_worker);


/**********************************************************************/
/*smd_core_init*/
int smd_core_init(void)
{
    int r;

    r = request_irq(INT_A9_M2A_0, smd_modem_irq_handler,
            IRQF_TRIGGER_RISING, "smd_dev", 0);
    if (r < 0)
        return r;
    r = enable_irq_wake(INT_A9_M2A_0);

    /* check for any SMD channels that may already exist */
    do_smd_probe();
}

static void do_smd_probe(void)
{
    struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE;
    if (shared->heap_info.free_offset != last_heap_free) {
        last_heap_free = shared->heap_info.free_offset;
        schedule_work(&probe_work);
    }
}

static void smd_channel_probe_worker(struct work_struct *work)
{
    /*
         *#define SMD_KIND_MASK        0xF00
    #define SMD_KIND_UNKNOWN    0x000
    #define SMD_KIND_STREAM        0x100
    #define SMD_KIND_PACKET        0x200
     */
    for (n = 0; n < 64; n++) {
        smd_alloc_channel(shared[n].name, shared[n].cid, ctype);
    }
}


static int smd_alloc_channel(const char *name, uint32_t cid, uint32_t type)
{
    struct smd_channel *ch;

    ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL);
    if (ch == 0) {
        pr_err("smd_alloc_channel() out of memory\n");
        return -1;
    }
    ch->n = cid;

    if (_smd_alloc_channel(ch)) {
        kfree(ch);
        return -1;
    }

    ch->fifo_mask = ch->fifo_size - 1;
    ch->type = type;

    if ((type & SMD_TYPE_MASK) == SMD_TYPE_APPS_MODEM)
        ch->notify_other_cpu = notify_modem_smd;
    else
        ch->notify_other_cpu = notify_dsp_smd;

    if (smd_is_packet(cid, type)) {
        ch->read = smd_packet_read;
        ch->write = smd_packet_write;
        ch->read_avail = smd_packet_read_avail;
        ch->write_avail = smd_packet_write_avail;
        ch->update_state = update_packet_state;
    } else {
        ch->read = smd_stream_read;
        ch->write = smd_stream_write;
        ch->read_avail = smd_stream_read_avail;
        ch->write_avail = smd_stream_write_avail;
        ch->update_state = update_stream_state;
    }

    if ((type & 0xff) == 0)
        memcpy(ch->name, "SMD_", 4);
    else
        memcpy(ch->name, "DSP_", 4);
    memcpy(ch->name + 4, name, 20);
    ch->name[23] = 0;
    ch->pdev.name = ch->name;
    ch->pdev.id = -1;

    pr_debug("smd_alloc_channel() cid=%02d size=%05d '%s'\n",
        ch->n, ch->fifo_size, ch->name);

    mutex_lock(&smd_creation_mutex);
    list_add(&ch->ch_list, &smd_ch_closed_list);
    mutex_unlock(&smd_creation_mutex);

    platform_device_register(&ch->pdev);
    return 0;
}

struct smd_channel {
    volatile struct smd_half_channel *send;
    volatile struct smd_half_channel *recv;
    unsigned char *send_data;
    unsigned char *recv_data;

    unsigned fifo_mask;
    unsigned fifo_size;
    unsigned current_packet;
    unsigned n;

    struct list_head ch_list;

    void *priv;
    void (*notify)(void *priv, unsigned flags);

    int (*read)(struct smd_channel *ch, void *data, int len);
    int (*write)(struct smd_channel *ch, const void *data, int len);
    int (*read_avail)(struct smd_channel *ch);
    int (*write_avail)(struct smd_channel *ch);

    void (*update_state)(struct smd_channel *ch);
    unsigned last_state;
    void (*notify_other_cpu)(void);
    unsigned type;

    char name[32];
    struct platform_device pdev;
};


static inline int _smd_alloc_channel(struct smd_channel *ch)
{
    struct smd_shared_v2 *shared2;
    void *buffer;
    unsigned buffer_sz;

    shared2 = smem_alloc(SMEM_SMD_BASE_ID + ch->n, sizeof(*shared2));
    buffer = smem_item(SMEM_SMD_FIFO_BASE_ID + ch->n, &buffer_sz);

    if (!buffer)
        return -1;

    /* buffer must be a power-of-two size */
    if (buffer_sz & (buffer_sz - 1))
        return -1;

    buffer_sz /= 2;
    ch->send = &shared2->ch0;
    ch->recv = &shared2->ch1;
    ch->send_data = buffer;
    ch->recv_data = buffer + buffer_sz;
    ch->fifo_size = buffer_sz;
    return 0;
}

/**********************************************************************/
static irqreturn_t smd_modem_irq_handler(int irq, void *data)
{
    handle_smd_irq(&smd_ch_list_modem, notify_modem_smd);
    return IRQ_HANDLED;
}


static void handle_smd_irq(struct list_head *list, void (*notify)(void))
{
    unsigned long flags;
    struct smd_channel *ch;
    int do_notify = 0;
    unsigned ch_flags;
    unsigned tmp;

    spin_lock_irqsave(&smd_lock, flags);
    list_for_each_entry(ch, list, ch_list) {
        ch_flags = 0;
        if (ch_is_open(ch)) {
            if (ch->recv->fHEAD) {
                ch->recv->fHEAD = 0;
                ch_flags |= 1;
                do_notify |= 1;
            }
            if (ch->recv->fTAIL) {
                ch->recv->fTAIL = 0;
                ch_flags |= 2;
                do_notify |= 1;
            }
            if (ch->recv->fSTATE) {
                ch->recv->fSTATE = 0;
                ch_flags |= 4;
                do_notify |= 1;
            }
        }
        tmp = ch->recv->state;
        if (tmp != ch->last_state)
            smd_state_change(ch, ch->last_state, tmp);
        if (ch_flags) {
            ch->update_state(ch);
            ch->notify(ch->priv, SMD_EVENT_DATA);
        }
    }
    if (do_notify)
        notify();
    spin_unlock_irqrestore(&smd_lock, flags);
    do_smd_probe();
}

/*以smd_ch_list_modem为线索*/

int smd_open(const char *name, smd_channel_t **_ch,
         void *priv, void (*notify)(void *, unsigned))
{
    struct smd_channel *ch;
    unsigned long flags;

    if (smd_initialized == 0) {
        pr_info("smd_open() before smd_init()\n");
        return -ENODEV;
    }

    ch = smd_get_channel(name);
    if (!ch)
        return -ENODEV;

    if (notify == 0)
        notify = do_nothing_notify;

    ch->notify = notify;
    ch->current_packet = 0;
    ch->last_state = SMD_SS_CLOSED;
    ch->priv = priv;

    *_ch = ch;

    spin_lock_irqsave(&smd_lock, flags);

    if ((ch->type & SMD_TYPE_MASK) == SMD_TYPE_APPS_MODEM)
        list_add(&ch->ch_list, &smd_ch_list_modem);
    else
        list_add(&ch->ch_list, &smd_ch_list_dsp);

    /* If the remote side is CLOSING, we need to get it to
     * move to OPENING (which we'll do by moving from CLOSED to
     * OPENING) and then get it to move from OPENING to
     * OPENED (by doing the same state change ourselves).
     *
     * Otherwise, it should be OPENING and we can move directly
     * to OPENED so that it will follow.
     */
    if (ch->recv->state == SMD_SS_CLOSING) {
        ch->send->head = 0;
        ch_set_state(ch, SMD_SS_OPENING);
    } else {
        ch_set_state(ch, SMD_SS_OPENED);
    }
    spin_unlock_irqrestore(&smd_lock, flags);
    smd_kick(ch);

    return 0;
}

static const struct tty_port_operations smd_tty_port_ops = {
    .shutdown = smd_tty_port_shutdown,
    .activate = smd_tty_port_activate,
};

static int smd_tty_port_activate(struct tty_port *tport, struct tty_struct *tty)
{
    int i, res = 0;
    int n = tty->index;
    const char *name = NULL;
    struct smd_tty_info *info = smd_tty + n;

    for (i = 0; i < smd_tty_channels_len; i++) {
        if (smd_tty_channels[i].id == n) {
            name = smd_tty_channels[i].name;
            break;
        }
    }
    if (!name)
        return -ENODEV;

    if (info->ch)
        smd_kick(info->ch);
    else
        res = smd_open(name, &info->ch, info, smd_tty_notify);

    if (!res)
        tty->driver_data = info;

    return res;
}

/*当中断发生时遍历smd_ch_list_modem并调用各个channel的ch->notify(ch->priv, SMD_EVENT_DATA);*/


static void smd_tty_notify(void *priv, unsigned event)
{
    unsigned char *ptr;
    int avail;
    struct smd_tty_info *info = priv;
    struct tty_struct *tty;

    if (event != SMD_EVENT_DATA)
        return;

    tty = tty_port_tty_get(&info->port);
    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) {
            /* shouldn't be possible since we're in interrupt
            ** context here and nobody else could 'steal' our
            ** characters.
            */
            pr_err("OOPS - smd_tty_buffer mismatch?!");
        }

        tty_flip_buffer_push(tty);
    }

    /* XXX only when writable and necessary */
    tty_wakeup(tty);
    tty_kref_put(tty);
}

struct smd_tty_info {
    struct tty_port port;
    smd_channel_t *ch;
};

/*********************************************************************************
 *
 * 所有的channel共享同一个 irq,当中断发生时可能多个channel都有数据需要读出
 *【通过状态判断】,这里是通过一个链表遍历各个channel的状态,调用注册的函数把数据读出
 *
 * 另外tty_prepare_flip_string/tty_flip_buffer_push都是在spinlock中调用,看样子这
 * 两个函数不会睡眠

 *********************************************************************************/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值