/* * Copyright (c) 2016 AllWinner Technology Co., Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #include #include #include #include #include #include #include #define EVT_MAX_SIZE 256 #define NETLINK_PCM_SUNXI 31 #undef SUNXI_NETLINK_TEST enum { SNDRV_PCM_NETLINK_CLOSE, SNDRV_PCM_NETLINK_START, SNDRV_PCM_NETLINK_NOUSE, }; #ifdef SUNXI_NETLINK_TEST static void sunxi_netlink_period(struct work_struct *ws); DECLARE_DELAYED_WORK(writer_work, sunxi_netlink_period); #endif struct evt_entry { struct list_head list; char evt_data[EVT_MAX_SIZE]; int size; }; struct xrun_event { int ref_cnt; struct sock *sock; struct list_head evtq; spinlock_t evt_lock; struct list_head freeq; struct work_struct ws; }; struct pcm_netlink_status { int pid; int status; struct mutex mutex_lock; }; static struct xrun_event pcm_event; static struct pcm_netlink_status pcm_netlink; static void free_event_entry(struct evt_entry *e) { kfree(e); } static inline struct evt_entry *alloc_evt_entry(void) { return kmalloc(sizeof(struct evt_entry), GFP_ATOMIC); } static struct evt_entry *get_evt_entry(void) { struct evt_entry *e; if (list_empty(&pcm_event.freeq)) e = alloc_evt_entry(); else { e = list_entry(pcm_event.freeq.next, struct evt_entry, list); list_del(&e->list); } return e; } static void put_evt_entry(struct evt_entry *e) { list_add_tail(&e->list, &pcm_event.freeq); } static int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len) { struct sk_buff *skb = NULL; struct nlmsghdr *nlh; int ret = 0; skb = nlmsg_new(len, GFP_ATOMIC); if (!skb) { kfree_skb(skb); return -EMSGSIZE; } nlh = nlmsg_put(skb, 0, 0, type, len, 0); if (!nlh) { kfree_skb(skb); return -EMSGSIZE; } memcpy(nlmsg_data(nlh), msg, len); NETLINK_CB(skb).portid = 0; NETLINK_CB(skb).dst_group = 0; if (pcm_netlink.status > SNDRV_PCM_NETLINK_CLOSE) { ret = netlink_unicast(sock, skb, pcm_netlink.pid, GFP_ATOMIC); if (ret < 0) pcm_netlink.status = SNDRV_PCM_NETLINK_CLOSE; } return ret; } static void _pcm_event_send(struct work_struct *work) { unsigned long flags; struct evt_entry *e; spin_lock_irqsave(&pcm_event.evt_lock, flags); while (!list_empty(&pcm_event.evtq)) { e = list_entry(pcm_event.evtq.next, struct evt_entry, list); spin_unlock_irqrestore(&pcm_event.evt_lock, flags); netlink_send(pcm_event.sock, 0, 0, e->evt_data, e->size); spin_lock_irqsave(&pcm_event.evt_lock, flags); list_del(&e->list); put_evt_entry(e); } spin_unlock_irqrestore(&pcm_event.evt_lock, flags); } static void netlink_rcv(struct sk_buff *skb) { struct nlmsghdr *nlh; int pid; nlh = (struct nlmsghdr *)skb->data; pid = nlh->nlmsg_pid; /*pid of sending process */ /* user start capture? */ if (!strncmp(nlmsg_data(nlh), "start", 5)) { pcm_netlink.pid = pid; pcm_netlink.status = SNDRV_PCM_NETLINK_START; } if (!strncmp(nlmsg_data(nlh), "close", 5)) pcm_netlink.status = SNDRV_PCM_NETLINK_CLOSE; #ifdef SUNXI_NETLINK_TEST if (pcm_netlink.status) schedule_delayed_work(&writer_work, 5 * HZ); #endif } static void printk_convert(struct evt_entry *e, const char *fmt, va_list args) { e->size = vscnprintf(e->evt_data, EVT_MAX_SIZE, fmt, args); /* mark and strip a trailing newline */ if (e->size && e->evt_data[e->size - 1] == '\n') e->size--; } void sunxi_netlink_printd(const char *fmt, ...) { va_list args; struct evt_entry *e; unsigned long flags; spin_lock_irqsave(&pcm_event.evt_lock, flags); e = get_evt_entry(); if (!e) { pr_err("pcm netlink memory not available\n"); spin_unlock_irqrestore(&pcm_event.evt_lock, flags); return; } va_start(args, fmt); printk_convert(e, fmt, args); va_end(args); list_add_tail(&e->list, &pcm_event.evtq); spin_unlock_irqrestore(&pcm_event.evt_lock, flags); schedule_work(&pcm_event.ws); } #ifdef SUNXI_NETLINK_TEST static void sunxi_netlink_period(struct work_struct *ws) { static int count = 1; sunxi_netlink_printd("hello form the pcm netlink test: %d\n", count); schedule_delayed_work(&writer_work, msecs_to_jiffies(10)); } #endif int sunxi_netlink_init(void) { struct netlink_kernel_cfg cfg = { .input = netlink_rcv, }; if (!pcm_event.ref_cnt) { pcm_event.sock = netlink_kernel_create(&init_net, NETLINK_PCM_SUNXI, &cfg); if (!pcm_event.sock) { pr_err("netlink create failed\n"); return -EMSGSIZE; } if (pcm_event.sock) { INIT_LIST_HEAD(&pcm_event.evtq); INIT_LIST_HEAD(&pcm_event.freeq); INIT_WORK(&pcm_event.ws, _pcm_event_send); spin_lock_init(&pcm_event.evt_lock); } pcm_netlink.status = SNDRV_PCM_NETLINK_CLOSE; } if (pcm_event.sock) { pcm_event.ref_cnt++; pr_debug("Creating PCM netlink successfully\n"); return 0; } pr_debug("Creating PCM netlink is failed\n"); return -EBUSY; } static void sunxi_netlink_exit(void) { struct evt_entry *e, *temp; unsigned long flags; if (pcm_event.sock && --pcm_event.ref_cnt == 0) { spin_lock_irqsave(&pcm_event.evt_lock, flags); list_for_each_entry_safe(e, temp, &pcm_event.evtq, list) { list_del(&e->list); free_event_entry(e); } list_for_each_entry_safe(e, temp, &pcm_event.freeq, list) { list_del(&e->list); free_event_entry(e); } spin_unlock_irqrestore(&pcm_event.evt_lock, flags); netlink_kernel_release(pcm_event.sock); pcm_event.sock = NULL; } } module_init(sunxi_netlink_init); module_exit(sunxi_netlink_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("wolfgang huang "); MODULE_DESCRIPTION("sunxi netlink interface"); MODULE_ALIAS("sunxi_netlink");