]> the.earth.li Git - esp8266-clock.git/blob - ota.c
Add initial OTA upgrade support
[esp8266-clock.git] / ota.c
1 /*
2  * Copyright 2018 Jonathan McDowell <noodles@earth.li>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include <user_interface.h>
18 #include <osapi.h>
19 #include <espconn.h>
20 #include <mem.h>
21 #include <stdlib.h>
22 #include <upgrade.h>
23
24 #include "ota.h"
25 #include "project_config.h"
26
27 struct ota_status {
28         struct espconn conn;
29         bool do_update;
30         uint8_t slot;
31         uint32_t rcvd_len;
32         uint32_t content_len;
33         ip_addr_t server_ip;
34 };
35
36 static void ICACHE_FLASH_ATTR ota_finish(struct ota_status *upgrade)
37 {
38         espconn_disconnect(&upgrade->conn);
39
40         if (system_upgrade_flag_check() == UPGRADE_FLAG_FINISH) {
41                 os_printf("Rebooting into new ROM.\n");
42                 system_upgrade_reboot();
43         } else {
44                 system_upgrade_flag_set(UPGRADE_FLAG_IDLE);
45         }
46
47         return;
48 }
49
50 static void ICACHE_FLASH_ATTR ota_receive(void *arg, char *buf,
51                 unsigned short len)
52 {
53         uint8_t maj, min;
54         char *verstr;
55         char *lenhdr, *data, *ptr;
56         struct ota_status *upgrade = arg;
57         int sectors;
58
59         if (!upgrade->do_update) {
60                 if ((os_strncmp(buf + 9, "200", 3) != 0)) {
61                         os_printf("Failed to fetch version info: %.3s\n",
62                                         buf + 9);
63                         return;
64                 }
65                 /* We're checking if we need an update; look for the version */
66                 verstr = os_strstr(buf, "ESP8266-Upgrade-Version: ");
67                 if (!verstr) {
68                         os_printf("Couldn't find version. Got data: %s\n",
69                                         buf);
70                         return;
71                 }
72                 verstr += strlen("ESP8266-Upgrade-Version: ");
73
74                 maj = strtol(verstr, &verstr, 10);
75                 if (*verstr != '.') {
76                         os_printf("Parsed major version %d, "
77                                         "but unexpected %c\n",
78                                         maj, *verstr);
79                 }
80                 verstr++;
81                 min = strtol(verstr, NULL, 10);
82
83                 os_printf("Got version %d.%d; I have version %d.%d\n",
84                                 maj, min, VER_MAJ, VER_MIN);
85                 if (maj > VER_MAJ || (maj == VER_MAJ && min > VER_MIN)) {
86                         os_printf("Need upgrade.\n");
87                         upgrade->do_update = true;
88                 }
89         } else {
90                 /* Trying to read the rom image */
91
92                 /* First reply? */
93                 if (upgrade->content_len == 0) {
94                         if ((lenhdr = os_strstr(buf, "Content-Length: ")) &&
95                                 (data = os_strstr(lenhdr, "\r\n\r\n")) &&
96                                 (os_strncmp(buf + 9, "200", 3) == 0)) {
97
98                                 data += 4;
99                                 len -= (data - buf);
100
101                                 lenhdr += 16; /* Content-Length:<sp> */
102                                 ptr = os_strstr(lenhdr, "\r\n");
103                                 *ptr = '\0';
104                                 upgrade->content_len = atoi(lenhdr);
105                                 os_printf("Reading %d bytes of image.\n,",
106                                                 upgrade->content_len);
107
108                                 if (upgrade->content_len > 0x6B000) {
109                                         os_printf("Image too large.\n");
110                                         upgrade->do_update = false;
111                                         ota_finish(upgrade);
112                                         return;
113                                 }
114
115                                 sectors = (upgrade->content_len +
116                                                 SPI_FLASH_SEC_SIZE - 1) >> 12;
117                                 while (sectors--) {
118                                         spi_flash_erase_sector(sectors +
119                                                 (upgrade->slot ? 129 : 1));
120                                 }
121
122                                 spi_flash_write(
123                                         (upgrade->slot ? 0x81000 : 0x1000),
124                                         (void *) data, len);
125                                 upgrade->rcvd_len = len;
126
127                         } else {
128                                 ptr = os_strstr(buf, "\r\n");
129                                 *ptr = '\0';
130                                 os_printf("Error getting ROM data: %s\n", buf);
131                                 upgrade->do_update = false;
132                                 ota_finish(upgrade);
133                                 return;
134                         }
135                 } else {
136                         spi_flash_write((upgrade->slot ? 0x81000 : 0x1000) +
137                                 upgrade->rcvd_len, (void *) buf, len);
138                         upgrade->rcvd_len += len;
139                 }
140
141                 if (upgrade->rcvd_len == upgrade->content_len) {
142                         upgrade->do_update = false;
143                         system_upgrade_flag_set(UPGRADE_FLAG_FINISH);
144                         ota_finish(upgrade);
145                 }
146         }
147 }
148
149 static void ICACHE_FLASH_ATTR ota_sent(void *arg)
150 {
151         /* Callback when all data sent by us down TCP connection */
152 }
153
154 static void ICACHE_FLASH_ATTR ota_connect(void *arg)
155 {
156         struct ota_status *upgrade = arg;
157         int len;
158         char buf[256];
159
160         espconn_regist_recvcb(&upgrade->conn, ota_receive);
161         espconn_regist_sentcb(&upgrade->conn, ota_sent);
162
163         if (upgrade->do_update) {
164                 os_printf("Sending rom image request header.\n");
165                 len = os_sprintf(buf, "GET %srom%d.bin HTTP/1.0\r\n"
166                         "Host: %s:%d\r\n"
167                         "Connection: close\r\n"
168                         "User-Agent: ESP8266 " PROJECT "\r\n"
169                         "\r\n",
170                         UPGRADE_PATH,
171                         upgrade->slot,
172                         UPGRADE_HOST,
173                         80);
174         } else {
175                 os_printf("Sending version check request header.\n");
176                 len = os_sprintf(buf, "GET %s%s HTTP/1.1\r\n"
177                         "Host: %s:%d\r\n"
178                         "Connection: close\r\n"
179                         "User-Agent: ESP8266 " PROJECT "\r\n"
180                         "\r\n",
181                         UPGRADE_PATH,
182                         "version.txt",
183                         UPGRADE_HOST,
184                         80);
185         }
186
187         espconn_send(&upgrade->conn, (uint8_t *) buf, len);
188 }
189
190 static void ICACHE_FLASH_ATTR ota_disconnect(void *arg)
191 {
192         struct ota_status *upgrade = arg;
193
194         if (upgrade == NULL) {
195                 return;
196         }
197
198         espconn_delete(&upgrade->conn);
199
200         if (!upgrade->do_update) {
201                 if (upgrade->conn.proto.tcp != NULL) {
202                         os_free(upgrade->conn.proto.tcp);
203                 }
204                 os_free(upgrade);
205                 system_upgrade_flag_set(UPGRADE_FLAG_IDLE);
206                 return;
207         }
208
209         /* Reuse conn for the rom download */
210         upgrade->conn.state = ESPCONN_NONE;
211
212         espconn_connect(&upgrade->conn);
213 }
214
215 static void ICACHE_FLASH_ATTR ota_error(void *arg, sint8 err)
216 {
217         os_printf("Upgrade disconnected with error: %d.\n", err);
218         ota_disconnect(arg);
219 }
220
221 static void ICACHE_FLASH_ATTR ota_got_dns(const char *name, ip_addr_t *ip,
222                 void *arg)
223 {
224         struct ota_status *upgrade = arg;
225
226         if (ip == NULL) {
227                 os_printf("Upgrade DNS request failed.\n");
228                 os_free(upgrade);
229                 system_upgrade_flag_set(UPGRADE_FLAG_IDLE);
230                 return;
231         }
232
233         upgrade->conn.type = ESPCONN_TCP;
234         upgrade->conn.state = ESPCONN_NONE;
235         upgrade->conn.proto.tcp = (esp_tcp *)os_malloc(sizeof(esp_tcp));
236         upgrade->conn.proto.tcp->local_port = espconn_port();
237         upgrade->conn.proto.tcp->remote_port = 80; /* FIXME; HTTPS */
238
239         os_memcpy(upgrade->conn.proto.tcp->remote_ip, &ip->addr, 4);
240
241         espconn_regist_connectcb(&upgrade->conn, ota_connect);
242         espconn_regist_disconcb(&upgrade->conn, ota_disconnect);
243         espconn_regist_reconcb(&upgrade->conn, ota_error);
244
245         espconn_connect(&upgrade->conn);
246 }
247
248 bool ICACHE_FLASH_ATTR ota_check()
249 {
250         struct ota_status *upgrade = NULL;
251
252         /* Don't start an upgrade if one is in progress */
253         if (system_upgrade_flag_check() == UPGRADE_FLAG_START) {
254                 return false;
255         }
256
257         upgrade = (struct ota_status *) os_zalloc(sizeof(struct ota_status));
258         if (!upgrade) {
259                 os_printf("Couldn't allocate memory for upgrade structure.\n");
260                 return false;
261         }
262
263         system_upgrade_flag_set(UPGRADE_FLAG_START);
264
265         upgrade->slot = system_upgrade_userbin_check() ? 0 : 1;
266
267         /* Kick off the DNS lookup to start */
268         espconn_gethostbyname(&upgrade->conn, UPGRADE_HOST,
269                 &upgrade->server_ip,
270                 ota_got_dns);
271
272         return true;
273 }