1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2021, MediaTek Inc. |
4 | * Copyright (c) 2021-2022, Intel Corporation. |
5 | * Copyright (c) 2024, Fibocom Wireless Inc. |
6 | * |
7 | * Authors: |
8 | * Amir Hanania <amir.hanania@intel.com> |
9 | * Chandrashekar Devegowda <chandrashekar.devegowda@intel.com> |
10 | * Haijun Liu <haijun.liu@mediatek.com> |
11 | * Moises Veleta <moises.veleta@intel.com> |
12 | * Ricardo Martinez <ricardo.martinez@linux.intel.com> |
13 | * |
14 | * Contributors: |
15 | * Andy Shevchenko <andriy.shevchenko@linux.intel.com> |
16 | * Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com> |
17 | * Eliot Lee <eliot.lee@intel.com> |
18 | * Sreehari Kancharla <sreehari.kancharla@intel.com> |
19 | * Jinjian Song <jinjian.song@fibocom.com> |
20 | */ |
21 | |
22 | #include <linux/atomic.h> |
23 | #include <linux/bitfield.h> |
24 | #include <linux/dev_printk.h> |
25 | #include <linux/err.h> |
26 | #include <linux/gfp.h> |
27 | #include <linux/minmax.h> |
28 | #include <linux/netdevice.h> |
29 | #include <linux/skbuff.h> |
30 | #include <linux/spinlock.h> |
31 | #include <linux/string.h> |
32 | #include <linux/wwan.h> |
33 | |
34 | #include "t7xx_port.h" |
35 | #include "t7xx_port_proxy.h" |
36 | #include "t7xx_state_monitor.h" |
37 | |
38 | static int t7xx_port_wwan_start(struct wwan_port *port) |
39 | { |
40 | struct t7xx_port *port_mtk = wwan_port_get_drvdata(port); |
41 | |
42 | if (atomic_read(v: &port_mtk->usage_cnt)) |
43 | return -EBUSY; |
44 | |
45 | atomic_inc(v: &port_mtk->usage_cnt); |
46 | return 0; |
47 | } |
48 | |
49 | static void t7xx_port_wwan_stop(struct wwan_port *port) |
50 | { |
51 | struct t7xx_port *port_mtk = wwan_port_get_drvdata(port); |
52 | |
53 | atomic_dec(v: &port_mtk->usage_cnt); |
54 | } |
55 | |
56 | static int t7xx_port_fastboot_tx(struct t7xx_port *port, struct sk_buff *skb) |
57 | { |
58 | struct sk_buff *cur = skb, *tx_skb; |
59 | size_t actual, len, offset = 0; |
60 | int txq_mtu; |
61 | int ret; |
62 | |
63 | txq_mtu = t7xx_get_port_mtu(port); |
64 | if (txq_mtu < 0) |
65 | return -EINVAL; |
66 | |
67 | actual = cur->len; |
68 | while (actual) { |
69 | len = min_t(size_t, actual, txq_mtu); |
70 | tx_skb = __dev_alloc_skb(length: len, GFP_KERNEL); |
71 | if (!tx_skb) |
72 | return -ENOMEM; |
73 | |
74 | skb_put_data(skb: tx_skb, data: cur->data + offset, len); |
75 | |
76 | ret = t7xx_port_send_raw_skb(port, skb: tx_skb); |
77 | if (ret) { |
78 | dev_kfree_skb(tx_skb); |
79 | dev_err(port->dev, "Write error on fastboot port, %d\n" , ret); |
80 | break; |
81 | } |
82 | offset += len; |
83 | actual -= len; |
84 | } |
85 | |
86 | dev_kfree_skb(skb); |
87 | return 0; |
88 | } |
89 | |
90 | static int t7xx_port_ctrl_tx(struct t7xx_port *port, struct sk_buff *skb) |
91 | { |
92 | const struct t7xx_port_conf *port_conf; |
93 | struct sk_buff *cur = skb, *cloned; |
94 | struct t7xx_fsm_ctl *ctl; |
95 | enum md_state md_state; |
96 | int cnt = 0, ret; |
97 | |
98 | port_conf = port->port_conf; |
99 | ctl = port->t7xx_dev->md->fsm_ctl; |
100 | md_state = t7xx_fsm_get_md_state(ctl); |
101 | if (md_state == MD_STATE_WAITING_FOR_HS1 || md_state == MD_STATE_WAITING_FOR_HS2) { |
102 | dev_warn(port->dev, "Cannot write to %s port when md_state=%d\n" , |
103 | port_conf->name, md_state); |
104 | return -ENODEV; |
105 | } |
106 | |
107 | while (cur) { |
108 | cloned = skb_clone(skb: cur, GFP_KERNEL); |
109 | cloned->len = skb_headlen(skb: cur); |
110 | ret = t7xx_port_send_skb(port, skb: cloned, pkt_header: 0, ex_msg: 0); |
111 | if (ret) { |
112 | dev_kfree_skb(cloned); |
113 | dev_err(port->dev, "Write error on %s port, %d\n" , |
114 | port_conf->name, ret); |
115 | return cnt ? cnt + ret : ret; |
116 | } |
117 | cnt += cur->len; |
118 | if (cur == skb) |
119 | cur = skb_shinfo(skb)->frag_list; |
120 | else |
121 | cur = cur->next; |
122 | } |
123 | |
124 | dev_kfree_skb(skb); |
125 | return 0; |
126 | } |
127 | |
128 | static int t7xx_port_wwan_tx(struct wwan_port *port, struct sk_buff *skb) |
129 | { |
130 | struct t7xx_port *port_private = wwan_port_get_drvdata(port); |
131 | const struct t7xx_port_conf *port_conf = port_private->port_conf; |
132 | int ret; |
133 | |
134 | if (!port_private->chan_enable) |
135 | return -EINVAL; |
136 | |
137 | if (port_conf->port_type != WWAN_PORT_FASTBOOT) |
138 | ret = t7xx_port_ctrl_tx(port: port_private, skb); |
139 | else |
140 | ret = t7xx_port_fastboot_tx(port: port_private, skb); |
141 | |
142 | return ret; |
143 | } |
144 | |
145 | static const struct wwan_port_ops wwan_ops = { |
146 | .start = t7xx_port_wwan_start, |
147 | .stop = t7xx_port_wwan_stop, |
148 | .tx = t7xx_port_wwan_tx, |
149 | }; |
150 | |
151 | static void t7xx_port_wwan_create(struct t7xx_port *port) |
152 | { |
153 | const struct t7xx_port_conf *port_conf = port->port_conf; |
154 | unsigned int = sizeof(struct ccci_header), mtu; |
155 | struct wwan_port_caps caps; |
156 | |
157 | if (!port->wwan.wwan_port) { |
158 | mtu = t7xx_get_port_mtu(port); |
159 | caps.frag_len = mtu - header_len; |
160 | caps.headroom_len = header_len; |
161 | port->wwan.wwan_port = wwan_create_port(parent: port->dev, type: port_conf->port_type, |
162 | ops: &wwan_ops, caps: &caps, drvdata: port); |
163 | if (IS_ERR(ptr: port->wwan.wwan_port)) |
164 | dev_err(port->dev, "Unable to create WWAN port %s" , port_conf->name); |
165 | } |
166 | } |
167 | |
168 | static int t7xx_port_wwan_init(struct t7xx_port *port) |
169 | { |
170 | const struct t7xx_port_conf *port_conf = port->port_conf; |
171 | |
172 | if (port_conf->port_type == WWAN_PORT_FASTBOOT) |
173 | t7xx_port_wwan_create(port); |
174 | |
175 | port->rx_length_th = RX_QUEUE_MAXLEN; |
176 | return 0; |
177 | } |
178 | |
179 | static void t7xx_port_wwan_uninit(struct t7xx_port *port) |
180 | { |
181 | if (!port->wwan.wwan_port) |
182 | return; |
183 | |
184 | port->rx_length_th = 0; |
185 | wwan_remove_port(port: port->wwan.wwan_port); |
186 | port->wwan.wwan_port = NULL; |
187 | } |
188 | |
189 | static int t7xx_port_wwan_recv_skb(struct t7xx_port *port, struct sk_buff *skb) |
190 | { |
191 | if (!atomic_read(v: &port->usage_cnt) || !port->chan_enable) { |
192 | const struct t7xx_port_conf *port_conf = port->port_conf; |
193 | |
194 | dev_kfree_skb_any(skb); |
195 | dev_err_ratelimited(port->dev, "Port %s is not opened, drop packets\n" , |
196 | port_conf->name); |
197 | /* Dropping skb, caller should not access skb.*/ |
198 | return 0; |
199 | } |
200 | |
201 | wwan_port_rx(port: port->wwan.wwan_port, skb); |
202 | return 0; |
203 | } |
204 | |
205 | static int t7xx_port_wwan_enable_chl(struct t7xx_port *port) |
206 | { |
207 | spin_lock(lock: &port->port_update_lock); |
208 | port->chan_enable = true; |
209 | spin_unlock(lock: &port->port_update_lock); |
210 | |
211 | return 0; |
212 | } |
213 | |
214 | static int t7xx_port_wwan_disable_chl(struct t7xx_port *port) |
215 | { |
216 | spin_lock(lock: &port->port_update_lock); |
217 | port->chan_enable = false; |
218 | spin_unlock(lock: &port->port_update_lock); |
219 | |
220 | return 0; |
221 | } |
222 | |
223 | static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int state) |
224 | { |
225 | const struct t7xx_port_conf *port_conf = port->port_conf; |
226 | |
227 | if (port_conf->port_type == WWAN_PORT_FASTBOOT) |
228 | return; |
229 | |
230 | if (state != MD_STATE_READY) |
231 | return; |
232 | |
233 | t7xx_port_wwan_create(port); |
234 | } |
235 | |
236 | struct port_ops wwan_sub_port_ops = { |
237 | .init = t7xx_port_wwan_init, |
238 | .recv_skb = t7xx_port_wwan_recv_skb, |
239 | .uninit = t7xx_port_wwan_uninit, |
240 | .enable_chl = t7xx_port_wwan_enable_chl, |
241 | .disable_chl = t7xx_port_wwan_disable_chl, |
242 | .md_state_notify = t7xx_port_wwan_md_state_notify, |
243 | }; |
244 | |