xref: /openbmc/linux/drivers/connector/connector.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
27672d0b5SEvgeniy Polyakov /*
37672d0b5SEvgeniy Polyakov  *	connector.c
47672d0b5SEvgeniy Polyakov  *
5acb9c1b2SEvgeniy Polyakov  * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
67672d0b5SEvgeniy Polyakov  * All rights reserved.
77672d0b5SEvgeniy Polyakov  */
87672d0b5SEvgeniy Polyakov 
9d2af686cSRandy Dunlap #include <linux/compiler.h>
107672d0b5SEvgeniy Polyakov #include <linux/kernel.h>
117672d0b5SEvgeniy Polyakov #include <linux/module.h>
127672d0b5SEvgeniy Polyakov #include <linux/list.h>
137672d0b5SEvgeniy Polyakov #include <linux/skbuff.h>
149631d79eSHong zhi guo #include <net/netlink.h>
157672d0b5SEvgeniy Polyakov #include <linux/moduleparam.h>
167672d0b5SEvgeniy Polyakov #include <linux/connector.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
188ed965d6SArjan van de Ven #include <linux/mutex.h>
19a0a61a60SLi Zefan #include <linux/proc_fs.h>
20a0a61a60SLi Zefan #include <linux/spinlock.h>
217672d0b5SEvgeniy Polyakov 
227672d0b5SEvgeniy Polyakov #include <net/sock.h>
237672d0b5SEvgeniy Polyakov 
247672d0b5SEvgeniy Polyakov MODULE_LICENSE("GPL");
25acb9c1b2SEvgeniy Polyakov MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
267672d0b5SEvgeniy Polyakov MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
273700c3c2SStephen Hemminger MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_CONNECTOR);
287672d0b5SEvgeniy Polyakov 
297672d0b5SEvgeniy Polyakov static struct cn_dev cdev;
307672d0b5SEvgeniy Polyakov 
3178374676SLi Zefan static int cn_already_initialized;
327672d0b5SEvgeniy Polyakov 
337672d0b5SEvgeniy Polyakov /*
3434470e0bSDavid Fries  * Sends mult (multiple) cn_msg at a time.
3534470e0bSDavid Fries  *
367672d0b5SEvgeniy Polyakov  * msg->seq and msg->ack are used to determine message genealogy.
377672d0b5SEvgeniy Polyakov  * When someone sends message it puts there locally unique sequence
387672d0b5SEvgeniy Polyakov  * and random acknowledge numbers.  Sequence number may be copied into
397672d0b5SEvgeniy Polyakov  * nlmsghdr->nlmsg_seq too.
407672d0b5SEvgeniy Polyakov  *
417672d0b5SEvgeniy Polyakov  * Sequence number is incremented with each message to be sent.
427672d0b5SEvgeniy Polyakov  *
43ac8f7330SDavid Fries  * If we expect a reply to our message then the sequence number in
447672d0b5SEvgeniy Polyakov  * received message MUST be the same as in original message, and
457672d0b5SEvgeniy Polyakov  * acknowledge number MUST be the same + 1.
467672d0b5SEvgeniy Polyakov  *
477672d0b5SEvgeniy Polyakov  * If we receive a message and its sequence number is not equal to the
487672d0b5SEvgeniy Polyakov  * one we are expecting then it is a new message.
497672d0b5SEvgeniy Polyakov  *
507672d0b5SEvgeniy Polyakov  * If we receive a message and its sequence number is the same as one
517672d0b5SEvgeniy Polyakov  * we are expecting but it's acknowledgement number is not equal to
527672d0b5SEvgeniy Polyakov  * the acknowledgement number in the original message + 1, then it is
537672d0b5SEvgeniy Polyakov  * a new message.
547672d0b5SEvgeniy Polyakov  *
5534470e0bSDavid Fries  * If msg->len != len, then additional cn_msg messages are expected following
5634470e0bSDavid Fries  * the first msg.
5734470e0bSDavid Fries  *
58ac8f7330SDavid Fries  * The message is sent to, the portid if given, the group if given, both if
59ac8f7330SDavid Fries  * both, or if both are zero then the group is looked up and sent there.
607672d0b5SEvgeniy Polyakov  */
cn_netlink_send_mult(struct cn_msg * msg,u16 len,u32 portid,u32 __group,gfp_t gfp_mask,int (* filter)(struct sock * dsk,struct sk_buff * skb,void * data),void * filter_data)6134470e0bSDavid Fries int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group,
622aa1f7a1SAnjali Kulkarni 	gfp_t gfp_mask,
632aa1f7a1SAnjali Kulkarni 	int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data),
642aa1f7a1SAnjali Kulkarni 	void *filter_data)
657672d0b5SEvgeniy Polyakov {
667672d0b5SEvgeniy Polyakov 	struct cn_callback_entry *__cbq;
677672d0b5SEvgeniy Polyakov 	unsigned int size;
687672d0b5SEvgeniy Polyakov 	struct sk_buff *skb;
697672d0b5SEvgeniy Polyakov 	struct nlmsghdr *nlh;
707672d0b5SEvgeniy Polyakov 	struct cn_msg *data;
717672d0b5SEvgeniy Polyakov 	struct cn_dev *dev = &cdev;
727672d0b5SEvgeniy Polyakov 	u32 group = 0;
737672d0b5SEvgeniy Polyakov 	int found = 0;
747672d0b5SEvgeniy Polyakov 
75ac8f7330SDavid Fries 	if (portid || __group) {
76ac8f7330SDavid Fries 		group = __group;
77ac8f7330SDavid Fries 	} else {
787672d0b5SEvgeniy Polyakov 		spin_lock_bh(&dev->cbdev->queue_lock);
797672d0b5SEvgeniy Polyakov 		list_for_each_entry(__cbq, &dev->cbdev->queue_list,
807672d0b5SEvgeniy Polyakov 				    callback_entry) {
81acd042bbSEvgeniy Polyakov 			if (cn_cb_equal(&__cbq->id.id, &msg->id)) {
827672d0b5SEvgeniy Polyakov 				found = 1;
837672d0b5SEvgeniy Polyakov 				group = __cbq->group;
84fd00eeccSLi Zefan 				break;
857672d0b5SEvgeniy Polyakov 			}
867672d0b5SEvgeniy Polyakov 		}
877672d0b5SEvgeniy Polyakov 		spin_unlock_bh(&dev->cbdev->queue_lock);
887672d0b5SEvgeniy Polyakov 
897672d0b5SEvgeniy Polyakov 		if (!found)
907672d0b5SEvgeniy Polyakov 			return -ENODEV;
917672d0b5SEvgeniy Polyakov 	}
927672d0b5SEvgeniy Polyakov 
93ac8f7330SDavid Fries 	if (!portid && !netlink_has_listeners(dev->nls, group))
94b191ba0dSEvgeniy Polyakov 		return -ESRCH;
95b191ba0dSEvgeniy Polyakov 
9634470e0bSDavid Fries 	size = sizeof(*msg) + len;
977672d0b5SEvgeniy Polyakov 
989631d79eSHong zhi guo 	skb = nlmsg_new(size, gfp_mask);
997672d0b5SEvgeniy Polyakov 	if (!skb)
1007672d0b5SEvgeniy Polyakov 		return -ENOMEM;
1017672d0b5SEvgeniy Polyakov 
1029631d79eSHong zhi guo 	nlh = nlmsg_put(skb, 0, msg->seq, NLMSG_DONE, size, 0);
10385c93166SJavier Martinez Canillas 	if (!nlh) {
10485c93166SJavier Martinez Canillas 		kfree_skb(skb);
10585c93166SJavier Martinez Canillas 		return -EMSGSIZE;
10685c93166SJavier Martinez Canillas 	}
1077672d0b5SEvgeniy Polyakov 
10885c93166SJavier Martinez Canillas 	data = nlmsg_data(nlh);
1097672d0b5SEvgeniy Polyakov 
110ac73bf50SMathias Krause 	memcpy(data, msg, size);
1117672d0b5SEvgeniy Polyakov 
1127672d0b5SEvgeniy Polyakov 	NETLINK_CB(skb).dst_group = group;
1137672d0b5SEvgeniy Polyakov 
114ac8f7330SDavid Fries 	if (group)
1152aa1f7a1SAnjali Kulkarni 		return netlink_broadcast_filtered(dev->nls, skb, portid, group,
1162aa1f7a1SAnjali Kulkarni 						  gfp_mask, filter,
1172aa1f7a1SAnjali Kulkarni 						  (void *)filter_data);
118d0164adcSMel Gorman 	return netlink_unicast(dev->nls, skb, portid,
119d0164adcSMel Gorman 			!gfpflags_allow_blocking(gfp_mask));
1207672d0b5SEvgeniy Polyakov }
12134470e0bSDavid Fries EXPORT_SYMBOL_GPL(cn_netlink_send_mult);
12234470e0bSDavid Fries 
12334470e0bSDavid Fries /* same as cn_netlink_send_mult except msg->len is used for len */
cn_netlink_send(struct cn_msg * msg,u32 portid,u32 __group,gfp_t gfp_mask)12434470e0bSDavid Fries int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group,
12534470e0bSDavid Fries 	gfp_t gfp_mask)
12634470e0bSDavid Fries {
1272aa1f7a1SAnjali Kulkarni 	return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask,
1282aa1f7a1SAnjali Kulkarni 				    NULL, NULL);
12934470e0bSDavid Fries }
130deb0e9b2SAndrew Morton EXPORT_SYMBOL_GPL(cn_netlink_send);
1317672d0b5SEvgeniy Polyakov 
1327672d0b5SEvgeniy Polyakov /*
1337672d0b5SEvgeniy Polyakov  * Callback helper - queues work and setup destructor for given data.
1347672d0b5SEvgeniy Polyakov  */
cn_call_callback(struct sk_buff * skb)135f1489cfbSPhilipp Reisner static int cn_call_callback(struct sk_buff *skb)
1367672d0b5SEvgeniy Polyakov {
137a30cfa47SDavid Fries 	struct nlmsghdr *nlh;
13804f482faSPatrick McHardy 	struct cn_callback_entry *i, *cbq = NULL;
1397672d0b5SEvgeniy Polyakov 	struct cn_dev *dev = &cdev;
1409631d79eSHong zhi guo 	struct cn_msg *msg = nlmsg_data(nlmsg_hdr(skb));
14104f482faSPatrick McHardy 	struct netlink_skb_parms *nsp = &NETLINK_CB(skb);
142acd042bbSEvgeniy Polyakov 	int err = -ENODEV;
1437672d0b5SEvgeniy Polyakov 
144a30cfa47SDavid Fries 	/* verify msg->len is within skb */
145a30cfa47SDavid Fries 	nlh = nlmsg_hdr(skb);
146a30cfa47SDavid Fries 	if (nlh->nlmsg_len < NLMSG_HDRLEN + sizeof(struct cn_msg) + msg->len)
147a30cfa47SDavid Fries 		return -EINVAL;
148a30cfa47SDavid Fries 
1497672d0b5SEvgeniy Polyakov 	spin_lock_bh(&dev->cbdev->queue_lock);
15004f482faSPatrick McHardy 	list_for_each_entry(i, &dev->cbdev->queue_list, callback_entry) {
15104f482faSPatrick McHardy 		if (cn_cb_equal(&i->id.id, &msg->id)) {
152e65f7ee3SElena Reshetova 			refcount_inc(&i->refcnt);
15304f482faSPatrick McHardy 			cbq = i;
1547672d0b5SEvgeniy Polyakov 			break;
1557672d0b5SEvgeniy Polyakov 		}
1567672d0b5SEvgeniy Polyakov 	}
1577672d0b5SEvgeniy Polyakov 	spin_unlock_bh(&dev->cbdev->queue_lock);
1587672d0b5SEvgeniy Polyakov 
15904f482faSPatrick McHardy 	if (cbq != NULL) {
16004f482faSPatrick McHardy 		cbq->callback(msg, nsp);
16104f482faSPatrick McHardy 		kfree_skb(skb);
16204f482faSPatrick McHardy 		cn_queue_release_callback(cbq);
1630e087858SPatrick McHardy 		err = 0;
16404f482faSPatrick McHardy 	}
16504f482faSPatrick McHardy 
166acd042bbSEvgeniy Polyakov 	return err;
1677672d0b5SEvgeniy Polyakov }
1687672d0b5SEvgeniy Polyakov 
169*bfdfdc2fSAnjali Kulkarni /*
170*bfdfdc2fSAnjali Kulkarni  * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC
171*bfdfdc2fSAnjali Kulkarni  * multicast group.
172*bfdfdc2fSAnjali Kulkarni  */
cn_bind(struct net * net,int group)173*bfdfdc2fSAnjali Kulkarni static int cn_bind(struct net *net, int group)
174*bfdfdc2fSAnjali Kulkarni {
175*bfdfdc2fSAnjali Kulkarni 	unsigned long groups = (unsigned long) group;
176*bfdfdc2fSAnjali Kulkarni 
177*bfdfdc2fSAnjali Kulkarni 	if (ns_capable(net->user_ns, CAP_NET_ADMIN))
178*bfdfdc2fSAnjali Kulkarni 		return 0;
179*bfdfdc2fSAnjali Kulkarni 
180*bfdfdc2fSAnjali Kulkarni 	if (test_bit(CN_IDX_PROC - 1, &groups))
181*bfdfdc2fSAnjali Kulkarni 		return 0;
182*bfdfdc2fSAnjali Kulkarni 
183*bfdfdc2fSAnjali Kulkarni 	return -EPERM;
184*bfdfdc2fSAnjali Kulkarni }
185*bfdfdc2fSAnjali Kulkarni 
cn_release(struct sock * sk,unsigned long * groups)1862aa1f7a1SAnjali Kulkarni static void cn_release(struct sock *sk, unsigned long *groups)
1872aa1f7a1SAnjali Kulkarni {
1882aa1f7a1SAnjali Kulkarni 	if (groups && test_bit(CN_IDX_PROC - 1, groups)) {
1892aa1f7a1SAnjali Kulkarni 		kfree(sk->sk_user_data);
1902aa1f7a1SAnjali Kulkarni 		sk->sk_user_data = NULL;
1912aa1f7a1SAnjali Kulkarni 	}
1922aa1f7a1SAnjali Kulkarni }
1932aa1f7a1SAnjali Kulkarni 
1947672d0b5SEvgeniy Polyakov /*
1957672d0b5SEvgeniy Polyakov  * Main netlink receiving function.
1967672d0b5SEvgeniy Polyakov  *
19700f5e06cSLi Zefan  * It checks skb, netlink header and msg sizes, and calls callback helper.
1987672d0b5SEvgeniy Polyakov  */
cn_rx_skb(struct sk_buff * skb)19955285bf0SFlorian Westphal static void cn_rx_skb(struct sk_buff *skb)
2007672d0b5SEvgeniy Polyakov {
2017672d0b5SEvgeniy Polyakov 	struct nlmsghdr *nlh;
202162b2bedSMathias Krause 	int len, err;
2037672d0b5SEvgeniy Polyakov 
2049631d79eSHong zhi guo 	if (skb->len >= NLMSG_HDRLEN) {
205b529ccf2SArnaldo Carvalho de Melo 		nlh = nlmsg_hdr(skb);
206162b2bedSMathias Krause 		len = nlmsg_len(nlh);
2077672d0b5SEvgeniy Polyakov 
208162b2bedSMathias Krause 		if (len < (int)sizeof(struct cn_msg) ||
2097672d0b5SEvgeniy Polyakov 		    skb->len < nlh->nlmsg_len ||
21055285bf0SFlorian Westphal 		    len > CONNECTOR_MAX_MSG_SIZE)
2116cf92e98SMichal Januszewski 			return;
2127672d0b5SEvgeniy Polyakov 
21355285bf0SFlorian Westphal 		err = cn_call_callback(skb_get(skb));
2147672d0b5SEvgeniy Polyakov 		if (err < 0)
2157672d0b5SEvgeniy Polyakov 			kfree_skb(skb);
2167672d0b5SEvgeniy Polyakov 	}
2177672d0b5SEvgeniy Polyakov }
2187672d0b5SEvgeniy Polyakov 
2197672d0b5SEvgeniy Polyakov /*
2207672d0b5SEvgeniy Polyakov  * Callback add routing - adds callback with given ID and name.
2217672d0b5SEvgeniy Polyakov  * If there is registered callback with the same ID it will not be added.
2227672d0b5SEvgeniy Polyakov  *
2237672d0b5SEvgeniy Polyakov  * May sleep.
2247672d0b5SEvgeniy Polyakov  */
cn_add_callback(const struct cb_id * id,const char * name,void (* callback)(struct cn_msg *,struct netlink_skb_parms *))225c18e6869SGeoff Levand int cn_add_callback(const struct cb_id *id, const char *name,
226f3c48eccSValentin Ilie 		    void (*callback)(struct cn_msg *,
227f3c48eccSValentin Ilie 				     struct netlink_skb_parms *))
2287672d0b5SEvgeniy Polyakov {
2297672d0b5SEvgeniy Polyakov 	struct cn_dev *dev = &cdev;
2307672d0b5SEvgeniy Polyakov 
231d6cc7f1aSEvgeniy Polyakov 	if (!cn_already_initialized)
232d6cc7f1aSEvgeniy Polyakov 		return -EAGAIN;
233d6cc7f1aSEvgeniy Polyakov 
234fe6bc89aSQinglang Miao 	return cn_queue_add_callback(dev->cbdev, name, id, callback);
2357672d0b5SEvgeniy Polyakov }
236deb0e9b2SAndrew Morton EXPORT_SYMBOL_GPL(cn_add_callback);
2377672d0b5SEvgeniy Polyakov 
2387672d0b5SEvgeniy Polyakov /*
2397672d0b5SEvgeniy Polyakov  * Callback remove routing - removes callback
2407672d0b5SEvgeniy Polyakov  * with given ID.
2417672d0b5SEvgeniy Polyakov  * If there is no registered callback with given
2427672d0b5SEvgeniy Polyakov  * ID nothing happens.
2437672d0b5SEvgeniy Polyakov  *
2447672d0b5SEvgeniy Polyakov  * May sleep while waiting for reference counter to become zero.
2457672d0b5SEvgeniy Polyakov  */
cn_del_callback(const struct cb_id * id)246c18e6869SGeoff Levand void cn_del_callback(const struct cb_id *id)
2477672d0b5SEvgeniy Polyakov {
2487672d0b5SEvgeniy Polyakov 	struct cn_dev *dev = &cdev;
2497672d0b5SEvgeniy Polyakov 
2507672d0b5SEvgeniy Polyakov 	cn_queue_del_callback(dev->cbdev, id);
2517672d0b5SEvgeniy Polyakov }
252deb0e9b2SAndrew Morton EXPORT_SYMBOL_GPL(cn_del_callback);
2537672d0b5SEvgeniy Polyakov 
cn_proc_show(struct seq_file * m,void * v)254d2af686cSRandy Dunlap static int __maybe_unused cn_proc_show(struct seq_file *m, void *v)
255a0a61a60SLi Zefan {
256a0a61a60SLi Zefan 	struct cn_queue_dev *dev = cdev.cbdev;
257a0a61a60SLi Zefan 	struct cn_callback_entry *cbq;
258a0a61a60SLi Zefan 
259a0a61a60SLi Zefan 	seq_printf(m, "Name            ID\n");
260a0a61a60SLi Zefan 
261a0a61a60SLi Zefan 	spin_lock_bh(&dev->queue_lock);
262a0a61a60SLi Zefan 
263a0a61a60SLi Zefan 	list_for_each_entry(cbq, &dev->queue_list, callback_entry) {
264a0a61a60SLi Zefan 		seq_printf(m, "%-15s %u:%u\n",
265a0a61a60SLi Zefan 			   cbq->id.name,
266a0a61a60SLi Zefan 			   cbq->id.id.idx,
267a0a61a60SLi Zefan 			   cbq->id.id.val);
268a0a61a60SLi Zefan 	}
269a0a61a60SLi Zefan 
270a0a61a60SLi Zefan 	spin_unlock_bh(&dev->queue_lock);
271a0a61a60SLi Zefan 
272a0a61a60SLi Zefan 	return 0;
273a0a61a60SLi Zefan }
274a0a61a60SLi Zefan 
cn_init(void)2750fe763c5SGreg Kroah-Hartman static int cn_init(void)
2767672d0b5SEvgeniy Polyakov {
2777672d0b5SEvgeniy Polyakov 	struct cn_dev *dev = &cdev;
278a31f2d17SPablo Neira Ayuso 	struct netlink_kernel_cfg cfg = {
279a31f2d17SPablo Neira Ayuso 		.groups	= CN_NETLINK_USERS + 0xf,
280903e9d1bSVasily Averin 		.input	= cn_rx_skb,
281*bfdfdc2fSAnjali Kulkarni 		.flags  = NL_CFG_F_NONROOT_RECV,
282*bfdfdc2fSAnjali Kulkarni 		.bind   = cn_bind,
2832aa1f7a1SAnjali Kulkarni 		.release = cn_release,
284a31f2d17SPablo Neira Ayuso 	};
2857672d0b5SEvgeniy Polyakov 
2869f00d977SPablo Neira Ayuso 	dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg);
2877672d0b5SEvgeniy Polyakov 	if (!dev->nls)
2887672d0b5SEvgeniy Polyakov 		return -EIO;
2897672d0b5SEvgeniy Polyakov 
2907672d0b5SEvgeniy Polyakov 	dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls);
2917672d0b5SEvgeniy Polyakov 	if (!dev->cbdev) {
292b7c6ba6eSDenis V. Lunev 		netlink_kernel_release(dev->nls);
2937672d0b5SEvgeniy Polyakov 		return -EINVAL;
2947672d0b5SEvgeniy Polyakov 	}
2957672d0b5SEvgeniy Polyakov 
296d6cc7f1aSEvgeniy Polyakov 	cn_already_initialized = 1;
297d6cc7f1aSEvgeniy Polyakov 
2983f3942acSChristoph Hellwig 	proc_create_single("connector", S_IRUGO, init_net.proc_net, cn_proc_show);
299a0a61a60SLi Zefan 
3007672d0b5SEvgeniy Polyakov 	return 0;
3017672d0b5SEvgeniy Polyakov }
3027672d0b5SEvgeniy Polyakov 
cn_fini(void)3030fe763c5SGreg Kroah-Hartman static void cn_fini(void)
3047672d0b5SEvgeniy Polyakov {
3057672d0b5SEvgeniy Polyakov 	struct cn_dev *dev = &cdev;
3067672d0b5SEvgeniy Polyakov 
3077672d0b5SEvgeniy Polyakov 	cn_already_initialized = 0;
3087672d0b5SEvgeniy Polyakov 
309ece31ffdSGao feng 	remove_proc_entry("connector", init_net.proc_net);
310a0a61a60SLi Zefan 
3117672d0b5SEvgeniy Polyakov 	cn_queue_free_dev(dev->cbdev);
312b7c6ba6eSDenis V. Lunev 	netlink_kernel_release(dev->nls);
3137672d0b5SEvgeniy Polyakov }
3147672d0b5SEvgeniy Polyakov 
315d6cc7f1aSEvgeniy Polyakov subsys_initcall(cn_init);
3167672d0b5SEvgeniy Polyakov module_exit(cn_fini);
317