aboutsummaryrefslogtreecommitdiff
path: root/net/caif/caif_chnlif.c
blob: c614b9aa004dd025ad0121b385d2b319842103e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
 * Copyright (C) ST-Ericsson AB 2010
 * Author:	Sjur Brendeland sjur.brandeland@stericsson.com
 * License terms: GNU General Public License (GPL) version 2
 */

#include <linux/skbuff.h>
#include <net/caif/caif_kernel.h>
#include <net/caif/caif_layer.h>
#include <net/caif/cfpkt.h>
#include <net/caif/cfcnfg.h>
#include <net/caif/caif_dev.h>
struct caif_kernelif {
	struct cflayer layer;
	struct caif_device *dev;
	struct cfctrl_link_param param;
};


/*
 * func caif_create_skb - Creates a CAIF SKB buffer
 * @data:		data to add to buffer
 * @data_length:	length of data
 */
struct sk_buff *caif_create_skb(unsigned char *data, unsigned int data_length)
{
	/* NOTE: Make room for CAIF headers when using SKB inside CAIF. */
	struct sk_buff *skb =
	    alloc_skb(data_length + CAIF_SKB_HEAD_RESERVE +
		      CAIF_SKB_TAIL_RESERVE, GFP_ATOMIC);
	if (skb == NULL)
		return NULL;
	skb_reserve(skb, CAIF_SKB_HEAD_RESERVE);

	memcpy(skb_put(skb, data_length), data, data_length);
	return skb;
}
EXPORT_SYMBOL(caif_create_skb);

int caif_extract_and_destroy_skb(struct sk_buff *skb, unsigned char *data,
			     unsigned int max_length)
{
	unsigned int len;
	len = skb->len;
	/*
	 * Note: skb_linearize only fails on an out of memory condition
	 * if we fail here we are NOT freeing the skb.
	 */
	if (!skb_linearize(skb) || skb->len > max_length)
		return -EOVERFLOW;
	memcpy(data, skb->data, skb->len);
	kfree_skb(skb);
	return len;
}
EXPORT_SYMBOL(caif_extract_and_destroy_skb);

/*
 * NOTE: transmit takes ownership of the SKB.
 *       I.e. transmit only fails on severe errors.
 *       flow_off is not checked on transmit; this is client's responcibility.
 */
int caif_transmit(struct caif_device *dev, struct sk_buff *skb)
{
	struct caif_kernelif *chnlif =
	    (struct caif_kernelif *) dev->_caif_handle;
	struct cfpkt *pkt;
	pkt = cfpkt_fromnative(CAIF_DIR_OUT, (void *) skb);
	return chnlif->layer.dn->transmit(chnlif->layer.dn, pkt);
}
EXPORT_SYMBOL(caif_transmit);

int caif_flow_control(struct caif_device *dev, enum caif_flowctrl flow)
{
	enum caif_modemcmd modemcmd;
	struct caif_kernelif *chnlif =
	    (struct caif_kernelif *) dev->_caif_handle;
	switch (flow) {
	case CAIF_FLOWCTRL_ON:
		modemcmd = CAIF_MODEMCMD_FLOW_ON_REQ;
		break;
	case CAIF_FLOWCTRL_OFF:
		modemcmd = CAIF_MODEMCMD_FLOW_OFF_REQ;
		break;
	default:
		return -EINVAL;
	}
	return chnlif->layer.dn->modemcmd(chnlif->layer.dn, modemcmd);
}
EXPORT_SYMBOL(caif_flow_control);

static int chnlif_receive(struct cflayer *layr, struct cfpkt *cfpkt)
{
	struct caif_kernelif *chnl =
		container_of(layr, struct caif_kernelif, layer);
	struct sk_buff *skb;
	skb = (struct sk_buff *) cfpkt_tonative(cfpkt);
	chnl->dev->receive_cb(chnl->dev, skb);
	return 0;
}

static void chnlif_flowctrl(struct cflayer *layr, enum caif_ctrlcmd ctrl,
			int phyid)
{
	struct caif_kernelif *chnl = (struct caif_kernelif *) layr;
	enum caif_control ctl;

	switch (ctrl) {
	case CAIF_CTRLCMD_FLOW_OFF_IND:
		ctl = CAIF_CONTROL_FLOW_OFF;
		break;
	case CAIF_CTRLCMD_FLOW_ON_IND:
		ctl = CAIF_CONTROL_FLOW_ON;
		break;
	case CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND:
		ctl = CAIF_CONTROL_REMOTE_SHUTDOWN;
		break;
	case CAIF_CTRLCMD_DEINIT_RSP:
		ctl = CAIF_CONTROL_DEV_DEINIT;
		chnl->dev->_caif_handle = NULL;
		chnl->dev->control_cb(chnl->dev, ctl);
		memset(chnl, 0, sizeof(chnl));
		kfree(chnl);
		return;

	case CAIF_CTRLCMD_INIT_RSP:
		ctl = CAIF_CONTROL_DEV_INIT;
		break;
	case CAIF_CTRLCMD_INIT_FAIL_RSP:
		ctl = CAIF_CONTROL_DEV_INIT_FAILED;
		break;
	default:
		return;
	}
	chnl->dev->control_cb(chnl->dev, ctl);
}

int caif_add_device(struct caif_device *dev)
{
	int ret;
	struct caif_kernelif *chnl = kmalloc(sizeof(struct caif_kernelif),
					     GFP_ATOMIC);
	if (!chnl)
		return -ENOMEM;
	chnl->dev = dev;
	chnl->layer.ctrlcmd = chnlif_flowctrl;
	chnl->layer.receive = chnlif_receive;
	ret =
	    channel_config_2_link_param(get_caif_conf(), &dev->caif_config,
				&chnl->param);
	if (ret < 0) {
		ret = -EINVAL;
		goto error;
	}
	if (cfcnfg_add_adaptation_layer(get_caif_conf(), &chnl->param,
				&chnl->layer)) {
		ret = -ENOTCONN;
		goto error;
	}
	dev->_caif_handle = chnl;

	return 0;
error:
	chnl->dev->_caif_handle = NULL;
	memset(chnl, 0, sizeof(chnl));
	kfree(chnl);
	return ret;
}
EXPORT_SYMBOL(caif_add_device);

int caif_remove_device(struct caif_device *caif_dev)
{

	struct caif_kernelif *chnl =
	    container_of(caif_dev->_caif_handle, struct caif_kernelif, layer);
	return cfcnfg_disconn_adapt_layer(get_caif_conf(), &chnl->layer);
}
EXPORT_SYMBOL(caif_remove_device);