]>
Commit | Line | Data |
---|---|---|
0efe1bcf AG |
1 | /* |
2 | * EFI application network access support | |
3 | * | |
4 | * Copyright (c) 2016 Alexander Graf | |
5 | * | |
6 | * SPDX-License-Identifier: GPL-2.0+ | |
7 | */ | |
8 | ||
9 | #include <common.h> | |
10 | #include <efi_loader.h> | |
11 | #include <inttypes.h> | |
12 | #include <lcd.h> | |
13 | #include <malloc.h> | |
14 | ||
15 | DECLARE_GLOBAL_DATA_PTR; | |
16 | ||
17 | static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_GUID; | |
18 | static const efi_guid_t efi_pxe_guid = EFI_PXE_GUID; | |
19 | static struct efi_pxe_packet *dhcp_ack; | |
20 | static bool new_rx_packet; | |
21 | static void *new_tx_packet; | |
a0549ef6 HS |
22 | /* |
23 | * The notification function of this event is called in every timer cycle | |
24 | * to check if a new network packet has been received. | |
25 | */ | |
26 | static struct efi_event *network_timer_event; | |
e5c21603 HS |
27 | /* |
28 | * This event is signaled when a packet has been received. | |
29 | */ | |
30 | static struct efi_event *wait_for_packet; | |
0efe1bcf AG |
31 | |
32 | struct efi_net_obj { | |
33 | /* Generic EFI object parent class data */ | |
34 | struct efi_object parent; | |
35 | /* EFI Interface callback struct for network */ | |
36 | struct efi_simple_network net; | |
37 | struct efi_simple_network_mode net_mode; | |
0efe1bcf AG |
38 | /* PXE struct to transmit dhcp data */ |
39 | struct efi_pxe pxe; | |
40 | struct efi_pxe_mode pxe_mode; | |
41 | }; | |
42 | ||
43 | static efi_status_t EFIAPI efi_net_start(struct efi_simple_network *this) | |
44 | { | |
45 | EFI_ENTRY("%p", this); | |
46 | ||
47 | return EFI_EXIT(EFI_SUCCESS); | |
48 | } | |
49 | ||
50 | static efi_status_t EFIAPI efi_net_stop(struct efi_simple_network *this) | |
51 | { | |
52 | EFI_ENTRY("%p", this); | |
53 | ||
54 | return EFI_EXIT(EFI_SUCCESS); | |
55 | } | |
56 | ||
57 | static efi_status_t EFIAPI efi_net_initialize(struct efi_simple_network *this, | |
58 | ulong extra_rx, ulong extra_tx) | |
59 | { | |
60 | EFI_ENTRY("%p, %lx, %lx", this, extra_rx, extra_tx); | |
61 | ||
62 | eth_init(); | |
63 | ||
64 | return EFI_EXIT(EFI_SUCCESS); | |
65 | } | |
66 | ||
67 | static efi_status_t EFIAPI efi_net_reset(struct efi_simple_network *this, | |
68 | int extended_verification) | |
69 | { | |
70 | EFI_ENTRY("%p, %x", this, extended_verification); | |
71 | ||
72 | return EFI_EXIT(EFI_SUCCESS); | |
73 | } | |
74 | ||
75 | static efi_status_t EFIAPI efi_net_shutdown(struct efi_simple_network *this) | |
76 | { | |
77 | EFI_ENTRY("%p", this); | |
78 | ||
79 | return EFI_EXIT(EFI_SUCCESS); | |
80 | } | |
81 | ||
82 | static efi_status_t EFIAPI efi_net_receive_filters( | |
83 | struct efi_simple_network *this, u32 enable, u32 disable, | |
84 | int reset_mcast_filter, ulong mcast_filter_count, | |
85 | struct efi_mac_address *mcast_filter) | |
86 | { | |
87 | EFI_ENTRY("%p, %x, %x, %x, %lx, %p", this, enable, disable, | |
88 | reset_mcast_filter, mcast_filter_count, mcast_filter); | |
89 | ||
61da678c | 90 | return EFI_EXIT(EFI_UNSUPPORTED); |
0efe1bcf AG |
91 | } |
92 | ||
93 | static efi_status_t EFIAPI efi_net_station_address( | |
94 | struct efi_simple_network *this, int reset, | |
95 | struct efi_mac_address *new_mac) | |
96 | { | |
97 | EFI_ENTRY("%p, %x, %p", this, reset, new_mac); | |
98 | ||
61da678c | 99 | return EFI_EXIT(EFI_UNSUPPORTED); |
0efe1bcf AG |
100 | } |
101 | ||
102 | static efi_status_t EFIAPI efi_net_statistics(struct efi_simple_network *this, | |
103 | int reset, ulong *stat_size, | |
104 | void *stat_table) | |
105 | { | |
106 | EFI_ENTRY("%p, %x, %p, %p", this, reset, stat_size, stat_table); | |
107 | ||
61da678c | 108 | return EFI_EXIT(EFI_UNSUPPORTED); |
0efe1bcf AG |
109 | } |
110 | ||
111 | static efi_status_t EFIAPI efi_net_mcastiptomac(struct efi_simple_network *this, | |
112 | int ipv6, | |
113 | struct efi_ip_address *ip, | |
114 | struct efi_mac_address *mac) | |
115 | { | |
116 | EFI_ENTRY("%p, %x, %p, %p", this, ipv6, ip, mac); | |
117 | ||
118 | return EFI_EXIT(EFI_INVALID_PARAMETER); | |
119 | } | |
120 | ||
121 | static efi_status_t EFIAPI efi_net_nvdata(struct efi_simple_network *this, | |
122 | int read_write, ulong offset, | |
123 | ulong buffer_size, char *buffer) | |
124 | { | |
125 | EFI_ENTRY("%p, %x, %lx, %lx, %p", this, read_write, offset, buffer_size, | |
126 | buffer); | |
127 | ||
61da678c | 128 | return EFI_EXIT(EFI_UNSUPPORTED); |
0efe1bcf AG |
129 | } |
130 | ||
131 | static efi_status_t EFIAPI efi_net_get_status(struct efi_simple_network *this, | |
132 | u32 *int_status, void **txbuf) | |
133 | { | |
134 | EFI_ENTRY("%p, %p, %p", this, int_status, txbuf); | |
135 | ||
891b3d90 HS |
136 | efi_timer_check(); |
137 | ||
138 | if (int_status) { | |
139 | /* We send packets synchronously, so nothing is outstanding */ | |
140 | *int_status = EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; | |
141 | if (new_rx_packet) | |
142 | *int_status |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; | |
143 | } | |
0efe1bcf AG |
144 | if (txbuf) |
145 | *txbuf = new_tx_packet; | |
146 | ||
147 | new_tx_packet = NULL; | |
148 | ||
149 | return EFI_EXIT(EFI_SUCCESS); | |
150 | } | |
151 | ||
152 | static efi_status_t EFIAPI efi_net_transmit(struct efi_simple_network *this, | |
8db174d6 | 153 | size_t header_size, size_t buffer_size, void *buffer, |
0efe1bcf AG |
154 | struct efi_mac_address *src_addr, |
155 | struct efi_mac_address *dest_addr, u16 *protocol) | |
156 | { | |
8db174d6 HS |
157 | EFI_ENTRY("%p, %lu, %lu, %p, %p, %p, %p", this, |
158 | (unsigned long)header_size, (unsigned long)buffer_size, | |
159 | buffer, src_addr, dest_addr, protocol); | |
0efe1bcf | 160 | |
a0549ef6 HS |
161 | efi_timer_check(); |
162 | ||
0efe1bcf AG |
163 | if (header_size) { |
164 | /* We would need to create the header if header_size != 0 */ | |
165 | return EFI_EXIT(EFI_INVALID_PARAMETER); | |
166 | } | |
167 | ||
712cd298 AG |
168 | #ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER |
169 | /* Ethernet packets always fit, just bounce */ | |
170 | memcpy(efi_bounce_buffer, buffer, buffer_size); | |
171 | net_send_packet(efi_bounce_buffer, buffer_size); | |
172 | #else | |
0efe1bcf | 173 | net_send_packet(buffer, buffer_size); |
712cd298 AG |
174 | #endif |
175 | ||
0efe1bcf AG |
176 | new_tx_packet = buffer; |
177 | ||
178 | return EFI_EXIT(EFI_SUCCESS); | |
179 | } | |
180 | ||
181 | static void efi_net_push(void *pkt, int len) | |
182 | { | |
183 | new_rx_packet = true; | |
e5c21603 | 184 | wait_for_packet->is_signaled = true; |
0efe1bcf AG |
185 | } |
186 | ||
8db174d6 HS |
187 | /* |
188 | * Receive a packet from a network interface. | |
189 | * | |
190 | * This function implements the Receive service of the Simple Network Protocol. | |
191 | * See the UEFI spec for details. | |
192 | * | |
193 | * @this the instance of the Simple Network Protocol | |
194 | * @header_size size of the media header | |
195 | * @buffer_size size of the buffer to receive the packet | |
196 | * @buffer buffer to receive the packet | |
197 | * @src_addr source MAC address | |
198 | * @dest_addr destination MAC address | |
199 | * @protocol protocol | |
200 | * @return status code | |
201 | */ | |
0efe1bcf | 202 | static efi_status_t EFIAPI efi_net_receive(struct efi_simple_network *this, |
8db174d6 | 203 | size_t *header_size, size_t *buffer_size, void *buffer, |
0efe1bcf AG |
204 | struct efi_mac_address *src_addr, |
205 | struct efi_mac_address *dest_addr, u16 *protocol) | |
206 | { | |
336d9dfc HS |
207 | struct ethernet_hdr *eth_hdr; |
208 | size_t hdr_size = sizeof(struct ethernet_hdr); | |
209 | u16 protlen; | |
210 | ||
0efe1bcf AG |
211 | EFI_ENTRY("%p, %p, %p, %p, %p, %p, %p", this, header_size, |
212 | buffer_size, buffer, src_addr, dest_addr, protocol); | |
213 | ||
a0549ef6 | 214 | efi_timer_check(); |
0efe1bcf AG |
215 | |
216 | if (!new_rx_packet) | |
217 | return EFI_EXIT(EFI_NOT_READY); | |
336d9dfc HS |
218 | /* Check that we at least received an Ethernet header */ |
219 | if (net_rx_packet_len < sizeof(struct ethernet_hdr)) { | |
220 | new_rx_packet = false; | |
221 | return EFI_EXIT(EFI_NOT_READY); | |
222 | } | |
223 | /* Fill export parameters */ | |
224 | eth_hdr = (struct ethernet_hdr *)net_rx_packet; | |
225 | protlen = ntohs(eth_hdr->et_protlen); | |
226 | if (protlen == 0x8100) { | |
227 | hdr_size += 4; | |
228 | protlen = ntohs(*(u16 *)&net_rx_packet[hdr_size - 2]); | |
229 | } | |
230 | if (header_size) | |
231 | *header_size = hdr_size; | |
232 | if (dest_addr) | |
233 | memcpy(dest_addr, eth_hdr->et_dest, ARP_HLEN); | |
234 | if (src_addr) | |
235 | memcpy(src_addr, eth_hdr->et_src, ARP_HLEN); | |
236 | if (protocol) | |
237 | *protocol = protlen; | |
0efe1bcf AG |
238 | if (*buffer_size < net_rx_packet_len) { |
239 | /* Packet doesn't fit, try again with bigger buf */ | |
240 | *buffer_size = net_rx_packet_len; | |
241 | return EFI_EXIT(EFI_BUFFER_TOO_SMALL); | |
242 | } | |
336d9dfc | 243 | /* Copy packet */ |
0efe1bcf AG |
244 | memcpy(buffer, net_rx_packet, net_rx_packet_len); |
245 | *buffer_size = net_rx_packet_len; | |
246 | new_rx_packet = false; | |
247 | ||
248 | return EFI_EXIT(EFI_SUCCESS); | |
249 | } | |
250 | ||
0efe1bcf AG |
251 | void efi_net_set_dhcp_ack(void *pkt, int len) |
252 | { | |
253 | int maxsize = sizeof(*dhcp_ack); | |
254 | ||
255 | if (!dhcp_ack) | |
256 | dhcp_ack = malloc(maxsize); | |
257 | ||
258 | memcpy(dhcp_ack, pkt, min(len, maxsize)); | |
259 | } | |
260 | ||
a0549ef6 HS |
261 | /* |
262 | * Check if a new network packet has been received. | |
263 | * | |
264 | * This notification function is called in every timer cycle. | |
265 | * | |
266 | * @event the event for which this notification function is registered | |
267 | * @context event context - not used in this function | |
268 | */ | |
269 | static void EFIAPI efi_network_timer_notify(struct efi_event *event, | |
270 | void *context) | |
271 | { | |
272 | EFI_ENTRY("%p, %p", event, context); | |
273 | ||
274 | if (!new_rx_packet) { | |
275 | push_packet = efi_net_push; | |
276 | eth_rx(); | |
277 | push_packet = NULL; | |
278 | } | |
279 | EFI_EXIT(EFI_SUCCESS); | |
280 | } | |
281 | ||
0efe1bcf | 282 | /* This gets called from do_bootefi_exec(). */ |
95c5553e | 283 | int efi_net_register(void) |
0efe1bcf AG |
284 | { |
285 | struct efi_net_obj *netobj; | |
a0549ef6 | 286 | efi_status_t r; |
0efe1bcf AG |
287 | |
288 | if (!eth_get_dev()) { | |
289 | /* No eth device active, don't expose any */ | |
290 | return 0; | |
291 | } | |
292 | ||
293 | /* We only expose the "active" eth device, so one is enough */ | |
294 | netobj = calloc(1, sizeof(*netobj)); | |
84d14568 HS |
295 | if (!netobj) |
296 | goto out_of_memory; | |
297 | ||
298 | /* Hook net up to the device list */ | |
69fb6b1a | 299 | INIT_LIST_HEAD(&netobj->parent.protocols); |
84d14568 | 300 | list_add_tail(&netobj->parent.link, &efi_obj_list); |
0efe1bcf AG |
301 | |
302 | /* Fill in object data */ | |
0efe1bcf | 303 | netobj->parent.handle = &netobj->net; |
84d14568 HS |
304 | r = efi_add_protocol(netobj->parent.handle, &efi_net_guid, |
305 | &netobj->net); | |
306 | if (r != EFI_SUCCESS) | |
307 | goto out_of_memory; | |
308 | r = efi_add_protocol(netobj->parent.handle, &efi_guid_device_path, | |
309 | efi_dp_from_eth()); | |
310 | if (r != EFI_SUCCESS) | |
311 | goto out_of_memory; | |
312 | r = efi_add_protocol(netobj->parent.handle, &efi_pxe_guid, | |
313 | &netobj->pxe); | |
314 | if (r != EFI_SUCCESS) | |
315 | goto out_of_memory; | |
bdecf974 | 316 | netobj->net.revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION; |
0efe1bcf AG |
317 | netobj->net.start = efi_net_start; |
318 | netobj->net.stop = efi_net_stop; | |
319 | netobj->net.initialize = efi_net_initialize; | |
320 | netobj->net.reset = efi_net_reset; | |
321 | netobj->net.shutdown = efi_net_shutdown; | |
322 | netobj->net.receive_filters = efi_net_receive_filters; | |
323 | netobj->net.station_address = efi_net_station_address; | |
324 | netobj->net.statistics = efi_net_statistics; | |
325 | netobj->net.mcastiptomac = efi_net_mcastiptomac; | |
326 | netobj->net.nvdata = efi_net_nvdata; | |
327 | netobj->net.get_status = efi_net_get_status; | |
328 | netobj->net.transmit = efi_net_transmit; | |
329 | netobj->net.receive = efi_net_receive; | |
330 | netobj->net.mode = &netobj->net_mode; | |
331 | netobj->net_mode.state = EFI_NETWORK_STARTED; | |
0efe1bcf | 332 | memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6); |
5d4a5ea9 | 333 | netobj->net_mode.hwaddr_size = ARP_HLEN; |
0efe1bcf AG |
334 | netobj->net_mode.max_packet_size = PKTSIZE; |
335 | ||
336 | netobj->pxe.mode = &netobj->pxe_mode; | |
337 | if (dhcp_ack) | |
338 | netobj->pxe_mode.dhcp_ack = *dhcp_ack; | |
339 | ||
e5c21603 HS |
340 | /* |
341 | * Create WaitForPacket event. | |
342 | */ | |
343 | r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, | |
344 | efi_network_timer_notify, NULL, | |
345 | &wait_for_packet); | |
346 | if (r != EFI_SUCCESS) { | |
347 | printf("ERROR: Failed to register network event\n"); | |
348 | return r; | |
349 | } | |
350 | netobj->net.wait_for_packet = wait_for_packet; | |
a0549ef6 HS |
351 | /* |
352 | * Create a timer event. | |
353 | * | |
354 | * The notification function is used to check if a new network packet | |
355 | * has been received. | |
356 | */ | |
357 | r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, | |
358 | efi_network_timer_notify, NULL, | |
359 | &network_timer_event); | |
360 | if (r != EFI_SUCCESS) { | |
361 | printf("ERROR: Failed to register network event\n"); | |
362 | return r; | |
363 | } | |
364 | /* Network is time critical, create event in every timer cyle */ | |
365 | r = efi_set_timer(network_timer_event, EFI_TIMER_PERIODIC, 0); | |
366 | if (r != EFI_SUCCESS) { | |
367 | printf("ERROR: Failed to set network timer\n"); | |
368 | return r; | |
369 | } | |
370 | ||
0efe1bcf | 371 | return 0; |
84d14568 HS |
372 | out_of_memory: |
373 | printf("ERROR: Out of memory\n"); | |
374 | return 1; | |
0efe1bcf | 375 | } |