/**********************************************************************/
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中调用,看样子这
* 两个函数不会睡眠
*********************************************************************************/
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中调用,看样子这
* 两个函数不会睡眠
*********************************************************************************/