]>
Commit | Line | Data |
---|---|---|
443d3191 DM |
1 | // SPDX-License-Identifier: BSD-2-Clause |
2 | /* | |
3 | * Copyright (C) 2023 The Android Open Source Project | |
4 | */ | |
5 | ||
6 | #include <common.h> | |
7 | #include <fastboot.h> | |
8 | #include <net.h> | |
9 | #include <net/fastboot_tcp.h> | |
10 | #include <net/tcp.h> | |
11 | ||
12 | static char command[FASTBOOT_COMMAND_LEN] = {0}; | |
13 | static char response[FASTBOOT_RESPONSE_LEN] = {0}; | |
14 | ||
15 | static const unsigned short handshake_length = 4; | |
16 | static const uchar *handshake = "FB01"; | |
17 | ||
18 | static u16 curr_sport; | |
19 | static u16 curr_dport; | |
20 | static u32 curr_tcp_seq_num; | |
21 | static u32 curr_tcp_ack_num; | |
22 | static unsigned int curr_request_len; | |
23 | static enum fastboot_tcp_state { | |
24 | FASTBOOT_CLOSED, | |
25 | FASTBOOT_CONNECTED, | |
26 | FASTBOOT_DISCONNECTING | |
27 | } state = FASTBOOT_CLOSED; | |
28 | ||
29 | static void fastboot_tcp_answer(u8 action, unsigned int len) | |
30 | { | |
31 | const u32 response_seq_num = curr_tcp_ack_num; | |
32 | const u32 response_ack_num = curr_tcp_seq_num + | |
33 | (curr_request_len > 0 ? curr_request_len : 1); | |
34 | ||
35 | net_send_tcp_packet(len, htons(curr_sport), htons(curr_dport), | |
36 | action, response_seq_num, response_ack_num); | |
37 | } | |
38 | ||
39 | static void fastboot_tcp_reset(void) | |
40 | { | |
41 | fastboot_tcp_answer(TCP_RST, 0); | |
42 | state = FASTBOOT_CLOSED; | |
43 | } | |
44 | ||
45 | static void fastboot_tcp_send_packet(u8 action, const uchar *data, unsigned int len) | |
46 | { | |
47 | uchar *pkt = net_get_async_tx_pkt_buf(); | |
48 | ||
49 | memset(pkt, '\0', PKTSIZE); | |
50 | pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2; | |
51 | memcpy(pkt, data, len); | |
52 | fastboot_tcp_answer(action, len); | |
53 | memset(pkt, '\0', PKTSIZE); | |
54 | } | |
55 | ||
56 | static void fastboot_tcp_send_message(const char *message, unsigned int len) | |
57 | { | |
58 | __be64 len_be = __cpu_to_be64(len); | |
59 | uchar *pkt = net_get_async_tx_pkt_buf(); | |
60 | ||
61 | memset(pkt, '\0', PKTSIZE); | |
62 | pkt += net_eth_hdr_size() + IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2; | |
63 | // Put first 8 bytes as a big endian message length | |
64 | memcpy(pkt, &len_be, 8); | |
65 | pkt += 8; | |
66 | memcpy(pkt, message, len); | |
67 | fastboot_tcp_answer(TCP_ACK | TCP_PUSH, len + 8); | |
68 | memset(pkt, '\0', PKTSIZE); | |
69 | } | |
70 | ||
71 | static void fastboot_tcp_handler_ipv4(uchar *pkt, u16 dport, | |
72 | struct in_addr sip, u16 sport, | |
73 | u32 tcp_seq_num, u32 tcp_ack_num, | |
74 | u8 action, unsigned int len) | |
75 | { | |
c8acbbbf | 76 | int fastboot_command_id; |
443d3191 DM |
77 | u64 command_size; |
78 | u8 tcp_fin = action & TCP_FIN; | |
79 | u8 tcp_push = action & TCP_PUSH; | |
80 | ||
81 | curr_sport = sport; | |
82 | curr_dport = dport; | |
83 | curr_tcp_seq_num = tcp_seq_num; | |
84 | curr_tcp_ack_num = tcp_ack_num; | |
85 | curr_request_len = len; | |
86 | ||
87 | switch (state) { | |
88 | case FASTBOOT_CLOSED: | |
89 | if (tcp_push) { | |
90 | if (len != handshake_length || | |
91 | strlen(pkt) != handshake_length || | |
92 | memcmp(pkt, handshake, handshake_length) != 0) { | |
93 | fastboot_tcp_reset(); | |
94 | break; | |
95 | } | |
96 | fastboot_tcp_send_packet(TCP_ACK | TCP_PUSH, | |
97 | handshake, handshake_length); | |
98 | state = FASTBOOT_CONNECTED; | |
99 | } | |
100 | break; | |
101 | case FASTBOOT_CONNECTED: | |
102 | if (tcp_fin) { | |
103 | fastboot_tcp_answer(TCP_FIN | TCP_ACK, 0); | |
104 | state = FASTBOOT_DISCONNECTING; | |
105 | break; | |
106 | } | |
107 | if (tcp_push) { | |
108 | // First 8 bytes is big endian message length | |
109 | command_size = __be64_to_cpu(*(u64 *)pkt); | |
110 | len -= 8; | |
111 | pkt += 8; | |
112 | ||
113 | // Only single packet messages are supported ATM | |
114 | if (strlen(pkt) != command_size) { | |
115 | fastboot_tcp_reset(); | |
116 | break; | |
117 | } | |
118 | strlcpy(command, pkt, len + 1); | |
c8acbbbf | 119 | fastboot_command_id = fastboot_handle_command(command, response); |
443d3191 | 120 | fastboot_tcp_send_message(response, strlen(response)); |
c8acbbbf DM |
121 | fastboot_handle_boot(fastboot_command_id, |
122 | strncmp("OKAY", response, 4) == 0); | |
443d3191 DM |
123 | } |
124 | break; | |
125 | case FASTBOOT_DISCONNECTING: | |
126 | if (tcp_push) | |
127 | state = FASTBOOT_CLOSED; | |
128 | break; | |
129 | } | |
130 | ||
131 | memset(command, 0, FASTBOOT_COMMAND_LEN); | |
132 | memset(response, 0, FASTBOOT_RESPONSE_LEN); | |
133 | curr_sport = 0; | |
134 | curr_dport = 0; | |
135 | curr_tcp_seq_num = 0; | |
136 | curr_tcp_ack_num = 0; | |
137 | curr_request_len = 0; | |
138 | } | |
139 | ||
140 | void fastboot_tcp_start_server(void) | |
141 | { | |
142 | printf("Using %s device\n", eth_get_name()); | |
143 | printf("Listening for fastboot command on tcp %pI4\n", &net_ip); | |
144 | ||
145 | tcp_set_tcp_handler(fastboot_tcp_handler_ipv4); | |
146 | } |