]>
Commit | Line | Data |
---|---|---|
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; | |
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; | |
27 | /* | |
28 | * This event is signaled when a packet has been received. | |
29 | */ | |
30 | static struct efi_event *wait_for_packet; | |
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; | |
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 | ||
90 | return EFI_EXIT(EFI_UNSUPPORTED); | |
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 | ||
99 | return EFI_EXIT(EFI_UNSUPPORTED); | |
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 | ||
108 | return EFI_EXIT(EFI_UNSUPPORTED); | |
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 | ||
128 | return EFI_EXIT(EFI_UNSUPPORTED); | |
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 | ||
136 | /* We send packets synchronously, so nothing is outstanding */ | |
137 | if (int_status) | |
138 | *int_status = 0; | |
139 | if (txbuf) | |
140 | *txbuf = new_tx_packet; | |
141 | ||
142 | new_tx_packet = NULL; | |
143 | ||
144 | return EFI_EXIT(EFI_SUCCESS); | |
145 | } | |
146 | ||
147 | static efi_status_t EFIAPI efi_net_transmit(struct efi_simple_network *this, | |
148 | ulong header_size, ulong buffer_size, void *buffer, | |
149 | struct efi_mac_address *src_addr, | |
150 | struct efi_mac_address *dest_addr, u16 *protocol) | |
151 | { | |
152 | EFI_ENTRY("%p, %lx, %lx, %p, %p, %p, %p", this, header_size, | |
153 | buffer_size, buffer, src_addr, dest_addr, protocol); | |
154 | ||
155 | efi_timer_check(); | |
156 | ||
157 | if (header_size) { | |
158 | /* We would need to create the header if header_size != 0 */ | |
159 | return EFI_EXIT(EFI_INVALID_PARAMETER); | |
160 | } | |
161 | ||
162 | #ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER | |
163 | /* Ethernet packets always fit, just bounce */ | |
164 | memcpy(efi_bounce_buffer, buffer, buffer_size); | |
165 | net_send_packet(efi_bounce_buffer, buffer_size); | |
166 | #else | |
167 | net_send_packet(buffer, buffer_size); | |
168 | #endif | |
169 | ||
170 | new_tx_packet = buffer; | |
171 | ||
172 | return EFI_EXIT(EFI_SUCCESS); | |
173 | } | |
174 | ||
175 | static void efi_net_push(void *pkt, int len) | |
176 | { | |
177 | new_rx_packet = true; | |
178 | wait_for_packet->is_signaled = true; | |
179 | } | |
180 | ||
181 | static efi_status_t EFIAPI efi_net_receive(struct efi_simple_network *this, | |
182 | ulong *header_size, ulong *buffer_size, void *buffer, | |
183 | struct efi_mac_address *src_addr, | |
184 | struct efi_mac_address *dest_addr, u16 *protocol) | |
185 | { | |
186 | EFI_ENTRY("%p, %p, %p, %p, %p, %p, %p", this, header_size, | |
187 | buffer_size, buffer, src_addr, dest_addr, protocol); | |
188 | ||
189 | efi_timer_check(); | |
190 | ||
191 | if (!new_rx_packet) | |
192 | return EFI_EXIT(EFI_NOT_READY); | |
193 | ||
194 | if (*buffer_size < net_rx_packet_len) { | |
195 | /* Packet doesn't fit, try again with bigger buf */ | |
196 | *buffer_size = net_rx_packet_len; | |
197 | return EFI_EXIT(EFI_BUFFER_TOO_SMALL); | |
198 | } | |
199 | ||
200 | memcpy(buffer, net_rx_packet, net_rx_packet_len); | |
201 | *buffer_size = net_rx_packet_len; | |
202 | new_rx_packet = false; | |
203 | ||
204 | return EFI_EXIT(EFI_SUCCESS); | |
205 | } | |
206 | ||
207 | void efi_net_set_dhcp_ack(void *pkt, int len) | |
208 | { | |
209 | int maxsize = sizeof(*dhcp_ack); | |
210 | ||
211 | if (!dhcp_ack) | |
212 | dhcp_ack = malloc(maxsize); | |
213 | ||
214 | memcpy(dhcp_ack, pkt, min(len, maxsize)); | |
215 | } | |
216 | ||
217 | /* | |
218 | * Check if a new network packet has been received. | |
219 | * | |
220 | * This notification function is called in every timer cycle. | |
221 | * | |
222 | * @event the event for which this notification function is registered | |
223 | * @context event context - not used in this function | |
224 | */ | |
225 | static void EFIAPI efi_network_timer_notify(struct efi_event *event, | |
226 | void *context) | |
227 | { | |
228 | EFI_ENTRY("%p, %p", event, context); | |
229 | ||
230 | if (!new_rx_packet) { | |
231 | push_packet = efi_net_push; | |
232 | eth_rx(); | |
233 | push_packet = NULL; | |
234 | } | |
235 | EFI_EXIT(EFI_SUCCESS); | |
236 | } | |
237 | ||
238 | /* This gets called from do_bootefi_exec(). */ | |
239 | int efi_net_register(void) | |
240 | { | |
241 | struct efi_net_obj *netobj; | |
242 | efi_status_t r; | |
243 | ||
244 | if (!eth_get_dev()) { | |
245 | /* No eth device active, don't expose any */ | |
246 | return 0; | |
247 | } | |
248 | ||
249 | /* We only expose the "active" eth device, so one is enough */ | |
250 | netobj = calloc(1, sizeof(*netobj)); | |
251 | ||
252 | /* Fill in object data */ | |
253 | netobj->parent.protocols[0].guid = &efi_net_guid; | |
254 | netobj->parent.protocols[0].protocol_interface = &netobj->net; | |
255 | netobj->parent.protocols[1].guid = &efi_guid_device_path; | |
256 | netobj->parent.protocols[1].protocol_interface = | |
257 | efi_dp_from_eth(); | |
258 | netobj->parent.protocols[2].guid = &efi_pxe_guid; | |
259 | netobj->parent.protocols[2].protocol_interface = &netobj->pxe; | |
260 | netobj->parent.handle = &netobj->net; | |
261 | netobj->net.revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION; | |
262 | netobj->net.start = efi_net_start; | |
263 | netobj->net.stop = efi_net_stop; | |
264 | netobj->net.initialize = efi_net_initialize; | |
265 | netobj->net.reset = efi_net_reset; | |
266 | netobj->net.shutdown = efi_net_shutdown; | |
267 | netobj->net.receive_filters = efi_net_receive_filters; | |
268 | netobj->net.station_address = efi_net_station_address; | |
269 | netobj->net.statistics = efi_net_statistics; | |
270 | netobj->net.mcastiptomac = efi_net_mcastiptomac; | |
271 | netobj->net.nvdata = efi_net_nvdata; | |
272 | netobj->net.get_status = efi_net_get_status; | |
273 | netobj->net.transmit = efi_net_transmit; | |
274 | netobj->net.receive = efi_net_receive; | |
275 | netobj->net.mode = &netobj->net_mode; | |
276 | netobj->net_mode.state = EFI_NETWORK_STARTED; | |
277 | memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6); | |
278 | netobj->net_mode.hwaddr_size = ARP_HLEN; | |
279 | netobj->net_mode.max_packet_size = PKTSIZE; | |
280 | ||
281 | netobj->pxe.mode = &netobj->pxe_mode; | |
282 | if (dhcp_ack) | |
283 | netobj->pxe_mode.dhcp_ack = *dhcp_ack; | |
284 | ||
285 | /* Hook net up to the device list */ | |
286 | list_add_tail(&netobj->parent.link, &efi_obj_list); | |
287 | ||
288 | /* | |
289 | * Create WaitForPacket event. | |
290 | */ | |
291 | r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, | |
292 | efi_network_timer_notify, NULL, | |
293 | &wait_for_packet); | |
294 | if (r != EFI_SUCCESS) { | |
295 | printf("ERROR: Failed to register network event\n"); | |
296 | return r; | |
297 | } | |
298 | netobj->net.wait_for_packet = wait_for_packet; | |
299 | /* | |
300 | * Create a timer event. | |
301 | * | |
302 | * The notification function is used to check if a new network packet | |
303 | * has been received. | |
304 | */ | |
305 | r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, | |
306 | efi_network_timer_notify, NULL, | |
307 | &network_timer_event); | |
308 | if (r != EFI_SUCCESS) { | |
309 | printf("ERROR: Failed to register network event\n"); | |
310 | return r; | |
311 | } | |
312 | /* Network is time critical, create event in every timer cyle */ | |
313 | r = efi_set_timer(network_timer_event, EFI_TIMER_PERIODIC, 0); | |
314 | if (r != EFI_SUCCESS) { | |
315 | printf("ERROR: Failed to set network timer\n"); | |
316 | return r; | |
317 | } | |
318 | ||
319 | return 0; | |
320 | } |