]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/staging/csr/csr_wifi_hip_xbv.c
staging: csr: remove CsrInt16 typedef
[karo-tx-linux.git] / drivers / staging / csr / csr_wifi_hip_xbv.c
1 /*****************************************************************************
2
3             (c) Cambridge Silicon Radio Limited 2012
4             All rights reserved and confidential information of CSR
5
6             Refer to LICENSE.txt included with this source for details
7             on the license terms.
8
9 *****************************************************************************/
10
11 /*
12  * ---------------------------------------------------------------------------
13  * FILE: csr_wifi_hip_xbv.c
14  *
15  * PURPOSE:
16  *      Routines for downloading firmware to UniFi.
17  *
18  *      UniFi firmware files use a nested TLV (Tag-Length-Value) format.
19  *
20  * ---------------------------------------------------------------------------
21  */
22
23 #ifdef CSR_WIFI_XBV_TEST
24 /* Standalone test harness */
25 #include "unifi_xbv.h"
26 #include "csr_wifi_hip_unifihw.h"
27 #else
28 /* Normal driver build */
29 #include "csr_wifi_hip_unifiversion.h"
30 #include "csr_wifi_hip_card.h"
31 #define DBG_TAG(t)
32 #endif
33
34 #include "csr_wifi_hip_xbv.h"
35
36 #define STREAM_CHECKSUM 0x6d34        /* Sum of uint16s in each patch stream */
37
38 /* XBV sizes used in patch conversion
39  */
40 #define PTDL_MAX_SIZE 2048            /* Max bytes allowed per PTDL */
41 #define PTDL_HDR_SIZE (4 + 2 + 6 + 2) /* sizeof(fw_id, sec_len, patch_cmd, csum) */
42
43 /* Struct to represent a buffer for reading firmware file */
44
45 typedef struct
46 {
47     void      *dlpriv;
48     CsrInt32   ioffset;
49     fwreadfn_t iread;
50 } ct_t;
51
52 /* Struct to represent a TLV field */
53 typedef struct
54 {
55     CsrCharString t_name[4];
56     CsrUint32     t_len;
57 } tag_t;
58
59
60 #define TAG_EQ(i, v)    (((i)[0] == (v)[0]) &&  \
61                          ((i)[1] == (v)[1]) &&  \
62                          ((i)[2] == (v)[2]) &&  \
63                          ((i)[3] == (v)[3]))
64
65 /* We create a small stack on the stack that contains an enum
66  * indicating the containing list segments, and the offset at which
67  * those lists end.  This enables a lot more error checking. */
68 typedef enum
69 {
70     xbv_xbv1,
71     /*xbv_info,*/
72     xbv_fw,
73     xbv_vers,
74     xbv_vand,
75     xbv_ptch,
76     xbv_other
77 } xbv_container;
78
79 #define XBV_STACK_SIZE 6
80 #define XBV_MAX_OFFS   0x7fffffff
81
82 typedef struct
83 {
84     struct
85     {
86         xbv_container container;
87         CsrInt32      ioffset_end;
88     } s[XBV_STACK_SIZE];
89     CsrUint32 ptr;
90 } xbv_stack_t;
91
92 static CsrInt32 read_tag(card_t *card, ct_t *ct, tag_t *tag);
93 static CsrInt32 read_bytes(card_t *card, ct_t *ct, void *buf, CsrUint32 len);
94 static CsrInt32 read_uint(card_t *card, ct_t *ct, CsrUint32 *u, CsrUint32 len);
95 static CsrInt32 xbv_check(xbv1_t *fwinfo, const xbv_stack_t *stack,
96                           xbv_mode new_mode, xbv_container old_cont);
97 static CsrInt32 xbv_push(xbv1_t *fwinfo, xbv_stack_t *stack,
98                          xbv_mode new_mode, xbv_container old_cont,
99                          xbv_container new_cont, CsrUint32 ioff);
100
101 static CsrUint32 write_uint16(void *buf, const CsrUint32 offset,
102                               const u16 val);
103 static CsrUint32 write_uint32(void *buf, const CsrUint32 offset,
104                               const CsrUint32 val);
105 static CsrUint32 write_bytes(void *buf, const CsrUint32 offset,
106                              const u8 *data, const CsrUint32 len);
107 static CsrUint32 write_tag(void *buf, const CsrUint32 offset,
108                            const CsrCharString *tag_str);
109 static CsrUint32 write_chunk(void *buf, const CsrUint32 offset,
110                              const CsrCharString *tag_str,
111                              const CsrUint32 payload_len);
112 static u16 calc_checksum(void *buf, const CsrUint32 offset,
113                                const CsrUint32 bytes_len);
114 static CsrUint32 calc_patch_size(const xbv1_t *fwinfo);
115
116 static CsrUint32 write_xbv_header(void *buf, const CsrUint32 offset,
117                                   const CsrUint32 file_payload_length);
118 static CsrUint32 write_ptch_header(void *buf, const CsrUint32 offset,
119                                    const CsrUint32 fw_id);
120 static CsrUint32 write_patchcmd(void *buf, const CsrUint32 offset,
121                                 const CsrUint32 dst_genaddr, const u16 len);
122 static CsrUint32 write_reset_ptdl(void *buf, const CsrUint32 offset,
123                                   const xbv1_t *fwinfo, CsrUint32 fw_id);
124 static CsrUint32 write_fwdl_to_ptdl(void *buf, const CsrUint32 offset,
125                                     fwreadfn_t readfn, const struct FWDL *fwdl,
126                                     const void *fw_buf, const CsrUint32 fw_id,
127                                     void *rdbuf);
128
129 /*
130  * ---------------------------------------------------------------------------
131  *  parse_xbv1
132  *
133  *      Scan the firmware file to find the TLVs we are interested in.
134  *      Actions performed:
135  *        - check we support the file format version in VERF
136  *      Store these TLVs if we have a firmware image:
137  *        - SLTP Symbol Lookup Table Pointer
138  *        - FWDL firmware download segments
139  *        - FWOL firmware overlay segment
140  *        - VMEQ Register probe tests to verify matching h/w
141  *      Store these TLVs if we have a patch file:
142  *        - FWID the firmware build ID that this file patches
143  *        - PTDL The actual patches
144  *
145  *      The structure pointed to by fwinfo is cleared and
146  *      'fwinfo->mode' is set to 'unknown'.  The 'fwinfo->mode'
147  *      variable is set to 'firmware' or 'patch' once we know which
148  *      sort of XBV file we have.
149  *
150  *  Arguments:
151  *      readfn          Pointer to function to call to read from the file.
152  *      dlpriv          Opaque pointer arg to pass to readfn.
153  *      fwinfo          Pointer to fwinfo struct to fill in.
154  *
155  *  Returns:
156  *      CSR_RESULT_SUCCESS on success, CSR error code on failure
157  * ---------------------------------------------------------------------------
158  */
159 CsrResult xbv1_parse(card_t *card, fwreadfn_t readfn, void *dlpriv, xbv1_t *fwinfo)
160 {
161     ct_t ct;
162     tag_t tag;
163     xbv_stack_t stack;
164
165     ct.dlpriv = dlpriv;
166     ct.ioffset = 0;
167     ct.iread = readfn;
168
169     CsrMemSet(fwinfo, 0, sizeof(xbv1_t));
170     fwinfo->mode = xbv_unknown;
171
172     /* File must start with XBV1 triplet */
173     if (read_tag(card, &ct, &tag) <= 0)
174     {
175         unifi_error(NULL, "File is not UniFi firmware\n");
176         return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
177     }
178
179     DBG_TAG(tag.t_name);
180
181     if (!TAG_EQ(tag.t_name, "XBV1"))
182     {
183         unifi_error(NULL, "File is not UniFi firmware (%s)\n", tag.t_name);
184         return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
185     }
186
187     stack.ptr = 0;
188     stack.s[stack.ptr].container = xbv_xbv1;
189     stack.s[stack.ptr].ioffset_end = XBV_MAX_OFFS;
190
191     /* Now scan the file */
192     while (1)
193     {
194         CsrInt32 n;
195
196         n = read_tag(card, &ct, &tag);
197         if (n < 0)
198         {
199             unifi_error(NULL, "No tag\n");
200             return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
201         }
202         if (n == 0)
203         {
204             /* End of file */
205             break;
206         }
207
208         DBG_TAG(tag.t_name);
209
210         /* File format version */
211         if (TAG_EQ(tag.t_name, "VERF"))
212         {
213             CsrUint32 version;
214
215             if (xbv_check(fwinfo, &stack, xbv_unknown, xbv_xbv1) ||
216                 (tag.t_len != 2) ||
217                 read_uint(card, &ct, &version, 2))
218             {
219                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
220             }
221             if (version != 0)
222             {
223                 unifi_error(NULL, "Unsupported firmware file version: %d.%d\n",
224                             version >> 8, version & 0xFF);
225                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
226             }
227         }
228         else if (TAG_EQ(tag.t_name, "LIST"))
229         {
230             CsrCharString name[4];
231             CsrUint32 list_end;
232
233             list_end = ct.ioffset + tag.t_len;
234
235             if (read_bytes(card, &ct, name, 4))
236             {
237                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
238             }
239
240             DBG_TAG(name);
241             if (TAG_EQ(name, "FW  "))
242             {
243                 if (xbv_push(fwinfo, &stack, xbv_firmware, xbv_xbv1, xbv_fw, list_end))
244                 {
245                     return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
246                 }
247             }
248             else if (TAG_EQ(name, "VERS"))
249             {
250                 if (xbv_push(fwinfo, &stack, xbv_firmware, xbv_fw, xbv_vers, list_end) ||
251                     (fwinfo->vers.num_vand != 0))
252                 {
253                     return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
254                 }
255             }
256             else if (TAG_EQ(name, "VAND"))
257             {
258                 struct VAND *vand;
259
260                 if (xbv_push(fwinfo, &stack, xbv_firmware, xbv_vers, xbv_vand, list_end) ||
261                     (fwinfo->vers.num_vand >= MAX_VAND))
262                 {
263                     return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
264                 }
265
266                 /* Get a new VAND */
267                 vand = fwinfo->vand + fwinfo->vers.num_vand++;
268
269                 /* Fill it in */
270                 vand->first = fwinfo->num_vmeq;
271                 vand->count = 0;
272             }
273             else if (TAG_EQ(name, "PTCH"))
274             {
275                 if (xbv_push(fwinfo, &stack, xbv_patch, xbv_xbv1, xbv_ptch, list_end))
276                 {
277                     return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
278                 }
279             }
280             else
281             {
282                 /* Skip over any other lists.  We dont bother to push
283                  * the new list type now as we would only pop it at
284                  * the end of the outer loop. */
285                 ct.ioffset += tag.t_len - 4;
286             }
287         }
288         else if (TAG_EQ(tag.t_name, "SLTP"))
289         {
290             CsrUint32 addr;
291
292             if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_fw) ||
293                 (tag.t_len != 4) ||
294                 (fwinfo->slut_addr != 0) ||
295                 read_uint(card, &ct, &addr, 4))
296             {
297                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
298             }
299
300             fwinfo->slut_addr = addr;
301         }
302         else if (TAG_EQ(tag.t_name, "FWDL"))
303         {
304             CsrUint32 addr;
305             struct FWDL *fwdl;
306
307             if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_fw) ||
308                 (fwinfo->num_fwdl >= MAX_FWDL) ||
309                 (read_uint(card, &ct, &addr, 4)))
310             {
311                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
312             }
313
314             fwdl = fwinfo->fwdl + fwinfo->num_fwdl++;
315
316             fwdl->dl_size = tag.t_len - 4;
317             fwdl->dl_addr = addr;
318             fwdl->dl_offset = ct.ioffset;
319
320             ct.ioffset += tag.t_len - 4;
321         }
322         else if (TAG_EQ(tag.t_name, "FWOV"))
323         {
324             if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_fw) ||
325                 (fwinfo->fwov.dl_size != 0) ||
326                 (fwinfo->fwov.dl_offset != 0))
327             {
328                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
329             }
330
331             fwinfo->fwov.dl_size = tag.t_len;
332             fwinfo->fwov.dl_offset = ct.ioffset;
333
334             ct.ioffset += tag.t_len;
335         }
336         else if (TAG_EQ(tag.t_name, "VMEQ"))
337         {
338             CsrUint32 temp[3];
339             struct VAND *vand;
340             struct VMEQ *vmeq;
341
342             if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_vand) ||
343                 (fwinfo->num_vmeq >= MAX_VMEQ) ||
344                 (fwinfo->vers.num_vand == 0) ||
345                 (tag.t_len != 8) ||
346                 read_uint(card, &ct, &temp[0], 4) ||
347                 read_uint(card, &ct, &temp[1], 2) ||
348                 read_uint(card, &ct, &temp[2], 2))
349             {
350                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
351             }
352
353             /* Get the last VAND */
354             vand = fwinfo->vand + (fwinfo->vers.num_vand - 1);
355
356             /* Get a new VMEQ */
357             vmeq = fwinfo->vmeq + fwinfo->num_vmeq++;
358
359             /* Note that this VAND contains another VMEQ */
360             vand->count++;
361
362             /* Fill in the VMEQ */
363             vmeq->addr = temp[0];
364             vmeq->mask = (u16)temp[1];
365             vmeq->value = (u16)temp[2];
366         }
367         else if (TAG_EQ(tag.t_name, "FWID"))
368         {
369             CsrUint32 build_id;
370
371             if (xbv_check(fwinfo, &stack, xbv_patch, xbv_ptch) ||
372                 (tag.t_len != 4) ||
373                 (fwinfo->build_id != 0) ||
374                 read_uint(card, &ct, &build_id, 4))
375             {
376                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
377             }
378
379             fwinfo->build_id = build_id;
380         }
381         else if (TAG_EQ(tag.t_name, "PTDL"))
382         {
383             struct PTDL *ptdl;
384
385             if (xbv_check(fwinfo, &stack, xbv_patch, xbv_ptch) ||
386                 (fwinfo->num_ptdl >= MAX_PTDL))
387             {
388                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
389             }
390
391             /* Allocate a new PTDL */
392             ptdl = fwinfo->ptdl + fwinfo->num_ptdl++;
393
394             ptdl->dl_size = tag.t_len;
395             ptdl->dl_offset = ct.ioffset;
396
397             ct.ioffset += tag.t_len;
398         }
399         else
400         {
401             /*
402              * If we get here it is a tag we are not interested in,
403              * just skip over it.
404              */
405             ct.ioffset += tag.t_len;
406         }
407
408         /* Check to see if we are at the end of the currently stacked
409          * segment.  We could finish more than one list at a time. */
410         while (ct.ioffset >= stack.s[stack.ptr].ioffset_end)
411         {
412             if (ct.ioffset > stack.s[stack.ptr].ioffset_end)
413             {
414                 unifi_error(NULL,
415                             "XBV file has overrun stack'd segment %d (%d > %d)\n",
416                             stack.ptr, ct.ioffset, stack.s[stack.ptr].ioffset_end);
417                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
418             }
419             if (stack.ptr <= 0)
420             {
421                 unifi_error(NULL, "XBV file has underrun stack pointer\n");
422                 return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
423             }
424             stack.ptr--;
425         }
426     }
427
428     if (stack.ptr != 0)
429     {
430         unifi_error(NULL, "Last list of XBV is not complete.\n");
431         return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
432     }
433
434     return CSR_RESULT_SUCCESS;
435 } /* xbv1_parse() */
436
437
438 /* Check the the XBV file is of a consistant sort (either firmware or
439  * patch) and that we are in the correct containing list type. */
440 static CsrInt32 xbv_check(xbv1_t *fwinfo, const xbv_stack_t *stack,
441                           xbv_mode new_mode, xbv_container old_cont)
442 {
443     /* If the new file mode is unknown the current packet could be in
444      * either (any) type of XBV file, and we cant make a decission at
445      * this time. */
446     if (new_mode != xbv_unknown)
447     {
448         if (fwinfo->mode == xbv_unknown)
449         {
450             fwinfo->mode = new_mode;
451         }
452         else if (fwinfo->mode != new_mode)
453         {
454             return -1;
455         }
456     }
457     /* If the current stack top doesn't match what we expect then the
458      * file is corrupt. */
459     if (stack->s[stack->ptr].container != old_cont)
460     {
461         return -1;
462     }
463     return 0;
464 }
465
466
467 /* Make checks as above and then enter a new list */
468 static CsrInt32 xbv_push(xbv1_t *fwinfo, xbv_stack_t *stack,
469                          xbv_mode new_mode, xbv_container old_cont,
470                          xbv_container new_cont, CsrUint32 new_ioff)
471 {
472     if (xbv_check(fwinfo, stack, new_mode, old_cont))
473     {
474         return -1;
475     }
476
477     /* Check that our stack won't overflow. */
478     if (stack->ptr >= (XBV_STACK_SIZE - 1))
479     {
480         return -1;
481     }
482
483     /* Add the new list element to the top of the stack. */
484     stack->ptr++;
485     stack->s[stack->ptr].container = new_cont;
486     stack->s[stack->ptr].ioffset_end = new_ioff;
487
488     return 0;
489 }
490
491
492 static CsrUint32 xbv2uint(u8 *ptr, CsrInt32 len)
493 {
494     CsrUint32 u = 0;
495     s16 i;
496
497     for (i = 0; i < len; i++)
498     {
499         CsrUint32 b;
500         b = ptr[i];
501         u += b << (i * 8);
502     }
503     return u;
504 }
505
506
507 static CsrInt32 read_tag(card_t *card, ct_t *ct, tag_t *tag)
508 {
509     u8 buf[8];
510     CsrInt32 n;
511
512     n = (*ct->iread)(card->ospriv, ct->dlpriv, ct->ioffset, buf, 8);
513     if (n <= 0)
514     {
515         return n;
516     }
517
518     /* read the tag and length */
519     if (n != 8)
520     {
521         return -1;
522     }
523
524     /* get section tag */
525     CsrMemCpy(tag->t_name, buf, 4);
526
527     /* get section length */
528     tag->t_len = xbv2uint(buf + 4, 4);
529
530     ct->ioffset += 8;
531
532     return 8;
533 } /* read_tag() */
534
535
536 static CsrInt32 read_bytes(card_t *card, ct_t *ct, void *buf, CsrUint32 len)
537 {
538     /* read the tag value */
539     if ((*ct->iread)(card->ospriv, ct->dlpriv, ct->ioffset, buf, len) != (CsrInt32)len)
540     {
541         return -1;
542     }
543
544     ct->ioffset += len;
545
546     return 0;
547 } /* read_bytes() */
548
549
550 static CsrInt32 read_uint(card_t *card, ct_t *ct, CsrUint32 *u, CsrUint32 len)
551 {
552     u8 buf[4];
553
554     /* Integer cannot be more than 4 bytes */
555     if (len > 4)
556     {
557         return -1;
558     }
559
560     if (read_bytes(card, ct, buf, len))
561     {
562         return -1;
563     }
564
565     *u = xbv2uint(buf, len);
566
567     return 0;
568 } /* read_uint() */
569
570
571 static CsrUint32 write_uint16(void *buf, const CsrUint32 offset, const u16 val)
572 {
573     u8 *dst = (u8 *)buf + offset;
574     *dst++ = (u8)(val & 0xff); /* LSB first */
575     *dst = (u8)(val >> 8);
576     return sizeof(u16);
577 }
578
579
580 static CsrUint32 write_uint32(void *buf, const CsrUint32 offset, const CsrUint32 val)
581 {
582     (void)write_uint16(buf, offset + 0, (u16)(val & 0xffff));
583     (void)write_uint16(buf, offset + 2, (u16)(val >> 16));
584     return sizeof(CsrUint32);
585 }
586
587
588 static CsrUint32 write_bytes(void *buf, const CsrUint32 offset, const u8 *data, const CsrUint32 len)
589 {
590     CsrUint32 i;
591     u8 *dst = (u8 *)buf + offset;
592
593     for (i = 0; i < len; i++)
594     {
595         *dst++ = *((u8 *)data + i);
596     }
597     return len;
598 }
599
600
601 static CsrUint32 write_tag(void *buf, const CsrUint32 offset, const CsrCharString *tag_str)
602 {
603     u8 *dst = (u8 *)buf + offset;
604     CsrMemCpy(dst, tag_str, 4);
605     return 4;
606 }
607
608
609 static CsrUint32 write_chunk(void *buf, const CsrUint32 offset, const CsrCharString *tag_str, const CsrUint32 payload_len)
610 {
611     CsrUint32 written = 0;
612     written += write_tag(buf, offset, tag_str);
613     written += write_uint32(buf, written + offset, (CsrUint32)payload_len);
614
615     return written;
616 }
617
618
619 static u16 calc_checksum(void *buf, const CsrUint32 offset, const CsrUint32 bytes_len)
620 {
621     CsrUint32 i;
622     u8 *src = (u8 *)buf + offset;
623     u16 sum = 0;
624     u16 val;
625
626     for (i = 0; i < bytes_len / 2; i++)
627     {
628         /* Contents copied to file is LE, host might not be */
629         val = (u16) * src++;         /* LSB */
630         val += (u16)(*src++) << 8;   /* MSB */
631         sum += val;
632     }
633
634     /* Total of uint16s in the stream plus the stored check value
635      * should equal STREAM_CHECKSUM when decoded.
636      */
637     return (STREAM_CHECKSUM - sum);
638 }
639
640
641 #define PTDL_RESET_DATA_SIZE  20  /* Size of reset vectors PTDL */
642
643 static CsrUint32 calc_patch_size(const xbv1_t *fwinfo)
644 {
645     s16 i;
646     CsrUint32 size = 0;
647
648     /*
649      * Work out how big an equivalent patch format file must be for this image.
650      * This only needs to be approximate, so long as it's large enough.
651      */
652     if (fwinfo->mode != xbv_firmware)
653     {
654         return 0;
655     }
656
657     /* Payload (which will get put into a series of PTDLs) */
658     for (i = 0; i < fwinfo->num_fwdl; i++)
659     {
660         size += fwinfo->fwdl[i].dl_size;
661     }
662
663     /* Another PTDL at the end containing reset vectors */
664     size += PTDL_RESET_DATA_SIZE;
665
666     /* PTDL headers. Add one for remainder, one for reset vectors */
667     size += ((fwinfo->num_fwdl / PTDL_MAX_SIZE) + 2) * PTDL_HDR_SIZE;
668
669     /* Another 1K sufficient to cover miscellaneous headers */
670     size += 1024;
671
672     return size;
673 }
674
675
676 static CsrUint32 write_xbv_header(void *buf, const CsrUint32 offset, const CsrUint32 file_payload_length)
677 {
678     CsrUint32 written = 0;
679
680     /* The length value given to the XBV chunk is the length of all subsequent
681      * contents of the file, excluding the 8 byte size of the XBV1 header itself
682      * (The added 6 bytes thus accounts for the size of the VERF)
683      */
684     written += write_chunk(buf, offset + written, (CsrCharString *)"XBV1", file_payload_length + 6);
685
686     written += write_chunk(buf, offset + written, (CsrCharString *)"VERF", 2);
687     written += write_uint16(buf,  offset + written, 0);      /* File version */
688
689     return written;
690 }
691
692
693 static CsrUint32 write_ptch_header(void *buf, const CsrUint32 offset, const CsrUint32 fw_id)
694 {
695     CsrUint32 written = 0;
696
697     /* LIST is written with a zero length, to be updated later */
698     written += write_chunk(buf, offset + written, (CsrCharString *)"LIST", 0);
699     written += write_tag(buf, offset + written, (CsrCharString *)"PTCH");        /* List type */
700
701     written += write_chunk(buf, offset + written, (CsrCharString *)"FWID", 4);
702     written += write_uint32(buf, offset + written, fw_id);
703
704
705     return written;
706 }
707
708
709 #define UF_REGION_PHY  1
710 #define UF_REGION_MAC  2
711 #define UF_MEMPUT_MAC  0x0000
712 #define UF_MEMPUT_PHY  0x1000
713
714 static CsrUint32 write_patchcmd(void *buf, const CsrUint32 offset, const CsrUint32 dst_genaddr, const u16 len)
715 {
716     CsrUint32 written = 0;
717     CsrUint32 region = (dst_genaddr >> 28);
718     u16 cmd_and_len = UF_MEMPUT_MAC;
719
720     if (region == UF_REGION_PHY)
721     {
722         cmd_and_len = UF_MEMPUT_PHY;
723     }
724     else if (region != UF_REGION_MAC)
725     {
726         return 0; /* invalid */
727     }
728
729     /* Write the command and data length */
730     cmd_and_len |= len;
731     written += write_uint16(buf, offset + written, cmd_and_len);
732
733     /* Write the destination generic address */
734     written += write_uint16(buf, offset + written, (u16)(dst_genaddr >> 16));
735     written += write_uint16(buf, offset + written, (u16)(dst_genaddr & 0xffff));
736
737     /* The data payload should be appended to the command */
738     return written;
739 }
740
741
742 static CsrUint32 write_fwdl_to_ptdl(void *buf, const CsrUint32 offset, fwreadfn_t readfn,
743                                     const struct FWDL *fwdl, const void *dlpriv,
744                                     const CsrUint32 fw_id, void *fw_buf)
745 {
746     CsrUint32 written = 0;
747     s16 chunks = 0;
748     CsrUint32 left = fwdl->dl_size;      /* Bytes left in this fwdl */
749     CsrUint32 dl_addr = fwdl->dl_addr;   /* Target address of fwdl image on XAP */
750     CsrUint32 dl_offs = fwdl->dl_offset; /* Offset of fwdl image data in source */
751     u16 csum;
752     CsrUint32 csum_start_offs;           /* first offset to include in checksum */
753     CsrUint32 sec_data_len;              /* section data byte count */
754     CsrUint32 sec_len;                   /* section data + header byte count */
755
756     /* FWDL maps to one or more PTDLs, as max size for a PTDL is 1K words */
757     while (left)
758     {
759         /* Calculate amount to be transferred */
760         sec_data_len = CSRMIN(left, PTDL_MAX_SIZE - PTDL_HDR_SIZE);
761         sec_len = sec_data_len + PTDL_HDR_SIZE;
762
763         /* Write PTDL header + entire PTDL size */
764         written += write_chunk(buf, offset + written, (CsrCharString *)"PTDL", sec_len);
765         /* bug digest implies 4 bytes of padding here, but that seems wrong */
766
767         /* Checksum starts here */
768         csum_start_offs = offset + written;
769
770         /* Patch-chunk header: fw_id. Note that this is in XAP word order */
771         written += write_uint16(buf, offset + written, (u16)(fw_id >> 16));
772         written += write_uint16(buf, offset + written, (u16)(fw_id & 0xffff));
773
774         /* Patch-chunk header: section length in uint16s */
775         written += write_uint16(buf, offset + written, (u16)(sec_len / 2));
776
777
778         /* Write the appropriate patch command for the data's destination ptr */
779         written += write_patchcmd(buf, offset + written, dl_addr, (u16)(sec_data_len / 2));
780
781         /* Write the data itself (limited to the max chunk length) */
782         if (readfn(NULL, (void *)dlpriv, dl_offs, fw_buf, sec_data_len) < 0)
783         {
784             return 0;
785         }
786
787         written += write_bytes(buf,
788                                offset + written,
789                                fw_buf,
790                                sec_data_len);
791
792         /* u16 checksum calculated over data written */
793         csum = calc_checksum(buf, csum_start_offs, written - (csum_start_offs - offset));
794         written += write_uint16(buf, offset + written, csum);
795
796         left -= sec_data_len;
797         dl_addr += sec_data_len;
798         dl_offs += sec_data_len;
799         chunks++;
800     }
801
802     return written;
803 }
804
805
806 #define SEC_CMD_LEN         ((4 + 2) * 2) /* sizeof(cmd, vector) per XAP */
807 #define PTDL_VEC_HDR_SIZE   (4 + 2 + 2)   /* sizeof(fw_id, sec_len, csum) */
808 #define UF_MAC_START_VEC    0x00c00000    /* Start address of image on MAC */
809 #define UF_PHY_START_VEC    0x00c00000    /* Start address of image on PHY */
810 #define UF_MAC_START_CMD    0x6000        /* MAC "Set start address" command */
811 #define UF_PHY_START_CMD    0x7000        /* PHY "Set start address" command */
812
813 static CsrUint32 write_reset_ptdl(void *buf, const CsrUint32 offset, const xbv1_t *fwinfo, CsrUint32 fw_id)
814 {
815     CsrUint32 written = 0;
816     u16 csum;
817     CsrUint32 csum_start_offs;                 /* first offset to include in checksum */
818     CsrUint32 sec_len;                         /* section data + header byte count */
819
820     sec_len = SEC_CMD_LEN + PTDL_VEC_HDR_SIZE; /* Total section byte length */
821
822     /* Write PTDL header + entire PTDL size */
823     written += write_chunk(buf, offset + written, (CsrCharString *)"PTDL", sec_len);
824
825     /* Checksum starts here */
826     csum_start_offs = offset + written;
827
828     /* Patch-chunk header: fw_id. Note that this is in XAP word order */
829     written += write_uint16(buf, offset + written, (u16)(fw_id >> 16));
830     written += write_uint16(buf, offset + written, (u16)(fw_id & 0xffff));
831
832     /* Patch-chunk header: section length in uint16s */
833     written += write_uint16(buf, offset + written, (u16)(sec_len / 2));
834
835     /*
836      * Restart addresses to be executed on subsequent loader restart command.
837      */
838
839     /* Setup the MAC start address, note word ordering */
840     written += write_uint16(buf, offset + written, UF_MAC_START_CMD);
841     written += write_uint16(buf, offset + written, (UF_MAC_START_VEC >> 16));
842     written += write_uint16(buf, offset + written, (UF_MAC_START_VEC & 0xffff));
843
844     /* Setup the PHY start address, note word ordering */
845     written += write_uint16(buf, offset + written, UF_PHY_START_CMD);
846     written += write_uint16(buf, offset + written, (UF_PHY_START_VEC >> 16));
847     written += write_uint16(buf, offset + written, (UF_PHY_START_VEC & 0xffff));
848
849     /* u16 checksum calculated over data written */
850     csum = calc_checksum(buf, csum_start_offs, written - (csum_start_offs - offset));
851     written += write_uint16(buf, offset + written, csum);
852
853     return written;
854 }
855
856
857 /*
858  * ---------------------------------------------------------------------------
859  *  read_slut
860  *
861  *      desc
862  *
863  *  Arguments:
864  *      readfn          Pointer to function to call to read from the file.
865  *      dlpriv          Opaque pointer arg to pass to readfn.
866  *      addr            Offset into firmware image of SLUT.
867  *      fwinfo          Pointer to fwinfo struct to fill in.
868  *
869  *  Returns:
870  *      Number of SLUT entries in the f/w, or -1 if the image was corrupt.
871  * ---------------------------------------------------------------------------
872  */
873 CsrInt32 xbv1_read_slut(card_t *card, fwreadfn_t readfn, void *dlpriv, xbv1_t *fwinfo,
874                         symbol_t *slut, CsrUint32 slut_len)
875 {
876     s16 i;
877     CsrInt32 offset;
878     CsrUint32 magic;
879     CsrUint32 count = 0;
880     ct_t ct;
881
882     if (fwinfo->mode != xbv_firmware)
883     {
884         return -1;
885     }
886
887     /* Find the d/l segment containing the SLUT */
888     /* This relies on the SLUT being entirely contained in one segment */
889     offset = -1;
890     for (i = 0; i < fwinfo->num_fwdl; i++)
891     {
892         if ((fwinfo->slut_addr >= fwinfo->fwdl[i].dl_addr) &&
893             (fwinfo->slut_addr < (fwinfo->fwdl[i].dl_addr + fwinfo->fwdl[i].dl_size)))
894         {
895             offset = fwinfo->fwdl[i].dl_offset +
896                      (fwinfo->slut_addr - fwinfo->fwdl[i].dl_addr);
897         }
898     }
899     if (offset < 0)
900     {
901         return -1;
902     }
903
904     ct.dlpriv = dlpriv;
905     ct.ioffset = offset;
906     ct.iread = readfn;
907
908     if (read_uint(card, &ct, &magic, 2))
909     {
910         return -1;
911     }
912     if (magic != SLUT_FINGERPRINT)
913     {
914         return -1;
915     }
916
917     while (count < slut_len)
918     {
919         CsrUint32 id, obj;
920
921         /* Read Symbol Id */
922         if (read_uint(card, &ct, &id, 2))
923         {
924             return -1;
925         }
926
927         /* Check for end of table marker */
928         if (id == CSR_SLT_END)
929         {
930             break;
931         }
932
933         /* Read Symbol Value */
934         if (read_uint(card, &ct, &obj, 4))
935         {
936             return -1;
937         }
938
939         slut[count].id  = (u16)id;
940         slut[count].obj = obj;
941         count++;
942     }
943
944     return count;
945 } /* read_slut() */
946
947
948 /*
949  * ---------------------------------------------------------------------------
950  *  xbv_to_patch
951  *
952  *      Convert (the relevant parts of) a firmware xbv file into a patch xbv
953  *
954  *  Arguments:
955  *      card
956  *      fw_buf - pointer to xbv firmware image
957  *      fwinfo - structure describing the firmware image
958  *      size   - pointer to location into which size of f/w is written.
959  *
960  *  Returns:
961  *      Pointer to firmware image, or NULL on error. Caller must free this
962  *      buffer via CsrMemFree() once it's finished with.
963  *
964  *  Notes:
965  *      The input fw_buf should have been checked via xbv1_parse prior to
966  *      calling this function, so the input image is assumed valid.
967  * ---------------------------------------------------------------------------
968  */
969 #define PTCH_LIST_SIZE 16         /* sizeof PTCH+FWID chunk in LIST header */
970
971 void* xbv_to_patch(card_t *card, fwreadfn_t readfn,
972                    const void *fw_buf, const xbv1_t *fwinfo, CsrUint32 *size)
973 {
974     void *patch_buf = NULL;
975     CsrUint32 patch_buf_size;
976     CsrUint32 payload_offs = 0;           /* Start of XBV payload */
977     s16 i;
978     CsrUint32 patch_offs = 0;
979     CsrUint32 list_len_offs = 0;          /* Offset of PTDL LIST length parameter */
980     CsrUint32 ptdl_start_offs = 0;        /* Offset of first PTDL chunk */
981     CsrUint32 fw_id;
982     void *rdbuf;
983
984     if (!fw_buf || !fwinfo || !card)
985     {
986         return NULL;
987     }
988
989     if (fwinfo->mode != xbv_firmware)
990     {
991         unifi_error(NULL, "Not a firmware file\n");
992         return NULL;
993     }
994
995     /* Pre-allocate read buffer for chunk conversion */
996     rdbuf = CsrMemAlloc(PTDL_MAX_SIZE);
997     if (!rdbuf)
998     {
999         unifi_error(card, "Couldn't alloc conversion buffer\n");
1000         return NULL;
1001     }
1002
1003     /* Loader requires patch file's build ID to match the running firmware's */
1004     fw_id = card->build_id;
1005
1006     /* Firmware XBV1 contains VERF, optional INFO, SLUT(s), FWDL(s)          */
1007     /* Other chunks should get skipped.                                      */
1008     /* VERF should be sanity-checked against chip version                    */
1009
1010     /* Patch    XBV1 contains VERF, optional INFO, PTCH                      */
1011     /*          PTCH contains FWID, optional INFO, PTDL(s), PTDL(start_vec)  */
1012     /* Each FWDL is split into PTDLs (each is 1024 XAP words max)            */
1013     /* Each PTDL contains running ROM f/w version, and checksum              */
1014     /* MAC/PHY reset addresses (known) are added into a final PTDL           */
1015
1016     /* The input image has already been parsed, and loaded into fwinfo, so we
1017      * can use that to build the output image
1018      */
1019     patch_buf_size = calc_patch_size(fwinfo);
1020
1021     patch_buf = (void *)CsrMemAlloc(patch_buf_size);
1022     if (!patch_buf)
1023     {
1024         CsrMemFree(rdbuf);
1025         unifi_error(NULL, "Can't malloc buffer for patch conversion\n");
1026         return NULL;
1027     }
1028
1029     CsrMemSet(patch_buf, 0xdd, patch_buf_size);
1030
1031     /* Write XBV + VERF headers */
1032     patch_offs += write_xbv_header(patch_buf, patch_offs, 0);
1033     payload_offs = patch_offs;
1034
1035     /* Write patch (LIST) header */
1036     list_len_offs = patch_offs + 4;    /* Save LIST.length offset for later update */
1037     patch_offs += write_ptch_header(patch_buf, patch_offs, fw_id);
1038
1039     /* Save start offset of the PTDL chunks */
1040     ptdl_start_offs = patch_offs;
1041
1042     /* Write LIST of firmware PTDL blocks */
1043     for (i = 0; i < fwinfo->num_fwdl; i++)
1044     {
1045         patch_offs += write_fwdl_to_ptdl(patch_buf,
1046                                          patch_offs,
1047                                          readfn,
1048                                          &fwinfo->fwdl[i],
1049                                          fw_buf,
1050                                          fw_id,
1051                                          rdbuf);
1052     }
1053
1054     /* Write restart-vector PTDL last */
1055     patch_offs += write_reset_ptdl(patch_buf, patch_offs, fwinfo, fw_id);
1056
1057     /* Now the length is known, update the LIST.length */
1058     (void)write_uint32(patch_buf, list_len_offs,
1059                        (patch_offs - ptdl_start_offs) + PTCH_LIST_SIZE);
1060
1061     /* Re write XBV headers just to fill in the correct file size */
1062     (void)write_xbv_header(patch_buf, 0, (patch_offs - payload_offs));
1063
1064     unifi_trace(card->ospriv, UDBG1, "XBV:PTCH size %u, fw_id %u\n",
1065                 patch_offs, fw_id);
1066     if (size)
1067     {
1068         *size = patch_offs;
1069     }
1070     CsrMemFree(rdbuf);
1071
1072     return patch_buf;
1073 }
1074
1075