1/* Disassemble MSP430 instructions.
2   Copyright (C) 2002, 2004, 2005, 2007 Free Software Foundation, Inc.
3
4   Contributed by Dmitry Diky <diwil@mail.ru>
5
6   This file is part of the GNU opcodes library.
7
8   This library is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 3, or (at your option)
11   any later version.
12
13   It is distributed in the hope that it will be useful, but WITHOUT
14   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
16   License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
21   MA 02110-1301, USA.  */
22
23#include <stdio.h>
24#include <ctype.h>
25#include <string.h>
26#include <sys/types.h>
27
28#include "dis-asm.h"
29#include "opintl.h"
30#include "libiberty.h"
31
32#define DASM_SECTION
33#include "opcode/msp430.h"
34#undef DASM_SECTION
35
36
37#define PS(x)   (0xffff & (x))
38
39static unsigned short
40msp430dis_opcode (bfd_vma addr, disassemble_info *info)
41{
42  bfd_byte buffer[2];
43  int status;
44
45  status = info->read_memory_func (addr, buffer, 2, info);
46  if (status != 0)
47    {
48      info->memory_error_func (status, addr, info);
49      return -1;
50    }
51  return bfd_getl16 (buffer);
52}
53
54static int
55msp430_nooperands (struct msp430_opcode_s *opcode,
56		   bfd_vma addr ATTRIBUTE_UNUSED,
57		   unsigned short insn ATTRIBUTE_UNUSED,
58		   char *comm,
59		   int *cycles)
60{
61  /* Pop with constant.  */
62  if (insn == 0x43b2)
63    return 0;
64  if (insn == opcode->bin_opcode)
65    return 2;
66
67  if (opcode->fmt == 0)
68    {
69      if ((insn & 0x0f00) != 3 || (insn & 0x0f00) != 2)
70	return 0;
71
72      strcpy (comm, "emulated...");
73      *cycles = 1;
74    }
75  else
76    {
77      strcpy (comm, "return from interupt");
78      *cycles = 5;
79    }
80
81  return 2;
82}
83
84static int
85msp430_singleoperand (disassemble_info *info,
86		      struct msp430_opcode_s *opcode,
87		      bfd_vma addr,
88		      unsigned short insn,
89		      char *op,
90		      char *comm,
91		      int *cycles)
92{
93  int regs = 0, regd = 0;
94  int ad = 0, as = 0;
95  int where = 0;
96  int cmd_len = 2;
97  short dst = 0;
98
99  regd = insn & 0x0f;
100  regs = (insn & 0x0f00) >> 8;
101  as = (insn & 0x0030) >> 4;
102  ad = (insn & 0x0080) >> 7;
103
104  switch (opcode->fmt)
105    {
106    case 0:			/* Emulated work with dst register.  */
107      if (regs != 2 && regs != 3 && regs != 1)
108	return 0;
109
110      /* Check if not clr insn.  */
111      if (opcode->bin_opcode == 0x4300 && (ad || as))
112	return 0;
113
114      /* Check if really inc, incd insns.  */
115      if ((opcode->bin_opcode & 0xff00) == 0x5300 && as == 3)
116	return 0;
117
118      if (ad == 0)
119	{
120	  *cycles = 1;
121
122	  /* Register.  */
123	  if (regd == 0)
124	    {
125	      *cycles += 1;
126	      sprintf (op, "r0");
127	    }
128	  else if (regd == 1)
129	    sprintf (op, "r1");
130
131	  else if (regd == 2)
132	    sprintf (op, "r2");
133
134	  else
135	    sprintf (op, "r%d", regd);
136	}
137      else	/* ad == 1 msp430dis_opcode.  */
138	{
139	  if (regd == 0)
140	    {
141	      /* PC relative.  */
142	      dst = msp430dis_opcode (addr + 2, info);
143	      cmd_len += 2;
144	      *cycles = 4;
145	      sprintf (op, "0x%04x", dst);
146	      sprintf (comm, "PC rel. abs addr 0x%04x",
147		       PS ((short) (addr + 2) + dst));
148	    }
149	  else if (regd == 2)
150	    {
151	      /* Absolute.  */
152	      dst = msp430dis_opcode (addr + 2, info);
153	      cmd_len += 2;
154	      *cycles = 4;
155	      sprintf (op, "&0x%04x", PS (dst));
156	    }
157	  else
158	    {
159	      dst = msp430dis_opcode (addr + 2, info);
160	      cmd_len += 2;
161	      *cycles = 4;
162	      sprintf (op, "%d(r%d)", dst, regd);
163	    }
164	}
165      break;
166
167    case 2:	/* rrc, push, call, swpb, rra, sxt, push, call, reti etc...  */
168      if (as == 0)
169	{
170	  if (regd == 3)
171	    {
172	      /* Constsnts.  */
173	      sprintf (op, "#0");
174	      sprintf (comm, "r3 As==00");
175	    }
176	  else
177	    {
178	      /* Register.  */
179	      sprintf (op, "r%d", regd);
180	    }
181	  *cycles = 1;
182	}
183      else if (as == 2)
184	{
185	  *cycles = 1;
186	  if (regd == 2)
187	    {
188	      sprintf (op, "#4");
189	      sprintf (comm, "r2 As==10");
190	    }
191	  else if (regd == 3)
192	    {
193	      sprintf (op, "#2");
194	      sprintf (comm, "r3 As==10");
195	    }
196	  else
197	    {
198	      *cycles = 3;
199	      /* Indexed register mode @Rn.  */
200	      sprintf (op, "@r%d", regd);
201	    }
202	}
203      else if (as == 3)
204	{
205	  *cycles = 1;
206	  if (regd == 2)
207	    {
208	      sprintf (op, "#8");
209	      sprintf (comm, "r2 As==11");
210	    }
211	  else if (regd == 3)
212	    {
213	      sprintf (op, "#-1");
214	      sprintf (comm, "r3 As==11");
215	    }
216	  else if (regd == 0)
217	    {
218	      *cycles = 3;
219	      /* absolute. @pc+ */
220	      dst = msp430dis_opcode (addr + 2, info);
221	      cmd_len += 2;
222	      sprintf (op, "#%d", dst);
223	      sprintf (comm, "#0x%04x", PS (dst));
224	    }
225	  else
226	    {
227	      *cycles = 3;
228	      sprintf (op, "@r%d+", regd);
229	    }
230	}
231      else if (as == 1)
232	{
233	  *cycles = 4;
234	  if (regd == 0)
235	    {
236	      /* PC relative.  */
237	      dst = msp430dis_opcode (addr + 2, info);
238	      cmd_len += 2;
239	      sprintf (op, "0x%04x", PS (dst));
240	      sprintf (comm, "PC rel. 0x%04x",
241		       PS ((short) addr + 2 + dst));
242	    }
243	  else if (regd == 2)
244	    {
245	      /* Absolute.  */
246	      dst = msp430dis_opcode (addr + 2, info);
247	      cmd_len += 2;
248	      sprintf (op, "&0x%04x", PS (dst));
249	    }
250	  else if (regd == 3)
251	    {
252	      *cycles = 1;
253	      sprintf (op, "#1");
254	      sprintf (comm, "r3 As==01");
255	    }
256	  else
257	    {
258	      /* Indexd.  */
259	      dst = msp430dis_opcode (addr + 2, info);
260	      cmd_len += 2;
261	      sprintf (op, "%d(r%d)", dst, regd);
262	    }
263	}
264      break;
265
266    case 3:			/* Jumps.  */
267      where = insn & 0x03ff;
268      if (where & 0x200)
269	where |= ~0x03ff;
270      if (where > 512 || where < -511)
271	return 0;
272
273      where *= 2;
274      sprintf (op, "$%+-8d", where + 2);
275      sprintf (comm, "abs 0x%x", PS ((short) (addr) + 2 + where));
276      *cycles = 2;
277      return 2;
278      break;
279    default:
280      cmd_len = 0;
281    }
282
283  return cmd_len;
284}
285
286static int
287msp430_doubleoperand (disassemble_info *info,
288		      struct msp430_opcode_s *opcode,
289		      bfd_vma addr,
290		      unsigned short insn,
291		      char *op1,
292		      char *op2,
293		      char *comm1,
294		      char *comm2,
295		      int *cycles)
296{
297  int regs = 0, regd = 0;
298  int ad = 0, as = 0;
299  int cmd_len = 2;
300  short dst = 0;
301
302  regd = insn & 0x0f;
303  regs = (insn & 0x0f00) >> 8;
304  as = (insn & 0x0030) >> 4;
305  ad = (insn & 0x0080) >> 7;
306
307  if (opcode->fmt == 0)
308    {
309      /* Special case: rla and rlc are the only 2 emulated instructions that
310	 fall into two operand instructions.  */
311      /* With dst, there are only:
312	 Rm       	Register,
313         x(Rm)     	Indexed,
314         0xXXXX    	Relative,
315         &0xXXXX    	Absolute
316         emulated_ins   dst
317         basic_ins      dst, dst.  */
318
319      if (regd != regs || as != ad)
320	return 0;		/* May be 'data' section.  */
321
322      if (ad == 0)
323	{
324	  /* Register mode.  */
325	  if (regd == 3)
326	    {
327	      strcpy (comm1, _("Illegal as emulation instr"));
328	      return -1;
329	    }
330
331	  sprintf (op1, "r%d", regd);
332	  *cycles = 1;
333	}
334      else			/* ad == 1 */
335	{
336	  if (regd == 0)
337	    {
338	      /* PC relative, Symbolic.  */
339	      dst = msp430dis_opcode (addr + 2, info);
340	      cmd_len += 4;
341	      *cycles = 6;
342	      sprintf (op1, "0x%04x", PS (dst));
343	      sprintf (comm1, "PC rel. 0x%04x",
344		       PS ((short) addr + 2 + dst));
345
346	    }
347	  else if (regd == 2)
348	    {
349	      /* Absolute.  */
350	      dst = msp430dis_opcode (addr + 2, info);
351	      /* If the 'src' field is not the same as the dst
352		 then this is not an rla instruction.  */
353	      if (dst != msp430dis_opcode (addr + 4, info))
354		return 0;
355	      cmd_len += 4;
356	      *cycles = 6;
357	      sprintf (op1, "&0x%04x", PS (dst));
358	    }
359	  else
360	    {
361	      /* Indexed.  */
362	      dst = msp430dis_opcode (addr + 2, info);
363	      cmd_len += 4;
364	      *cycles = 6;
365	      sprintf (op1, "%d(r%d)", dst, regd);
366	    }
367	}
368
369      *op2 = 0;
370      *comm2 = 0;
371      return cmd_len;
372    }
373
374  /* Two operands exactly.  */
375  if (ad == 0 && regd == 3)
376    {
377      /* R2/R3 are illegal as dest: may be data section.  */
378      strcpy (comm1, _("Illegal as 2-op instr"));
379      return -1;
380    }
381
382  /* Source.  */
383  if (as == 0)
384    {
385      *cycles = 1;
386      if (regs == 3)
387	{
388	  /* Constsnts.  */
389	  sprintf (op1, "#0");
390	  sprintf (comm1, "r3 As==00");
391	}
392      else
393	{
394	  /* Register.  */
395	  sprintf (op1, "r%d", regs);
396	}
397    }
398  else if (as == 2)
399    {
400      *cycles = 1;
401
402      if (regs == 2)
403	{
404	  sprintf (op1, "#4");
405	  sprintf (comm1, "r2 As==10");
406	}
407      else if (regs == 3)
408	{
409	  sprintf (op1, "#2");
410	  sprintf (comm1, "r3 As==10");
411	}
412      else
413	{
414	  *cycles = 2;
415
416	  /* Indexed register mode @Rn.  */
417	  sprintf (op1, "@r%d", regs);
418	}
419      if (!regs)
420	*cycles = 3;
421    }
422  else if (as == 3)
423    {
424      if (regs == 2)
425	{
426	  sprintf (op1, "#8");
427	  sprintf (comm1, "r2 As==11");
428	  *cycles = 1;
429	}
430      else if (regs == 3)
431	{
432	  sprintf (op1, "#-1");
433	  sprintf (comm1, "r3 As==11");
434	  *cycles = 1;
435	}
436      else if (regs == 0)
437	{
438	  *cycles = 3;
439	  /* Absolute. @pc+.  */
440	  dst = msp430dis_opcode (addr + 2, info);
441	  cmd_len += 2;
442	  sprintf (op1, "#%d", dst);
443	  sprintf (comm1, "#0x%04x", PS (dst));
444	}
445      else
446	{
447	  *cycles = 2;
448	  sprintf (op1, "@r%d+", regs);
449	}
450    }
451  else if (as == 1)
452    {
453      if (regs == 0)
454	{
455	  *cycles = 4;
456	  /* PC relative.  */
457	  dst = msp430dis_opcode (addr + 2, info);
458	  cmd_len += 2;
459	  sprintf (op1, "0x%04x", PS (dst));
460	  sprintf (comm1, "PC rel. 0x%04x",
461		   PS ((short) addr + 2 + dst));
462	}
463      else if (regs == 2)
464	{
465	  *cycles = 2;
466	  /* Absolute.  */
467	  dst = msp430dis_opcode (addr + 2, info);
468	  cmd_len += 2;
469	  sprintf (op1, "&0x%04x", PS (dst));
470	  sprintf (comm1, "0x%04x", PS (dst));
471	}
472      else if (regs == 3)
473	{
474	  *cycles = 1;
475	  sprintf (op1, "#1");
476	  sprintf (comm1, "r3 As==01");
477	}
478      else
479	{
480	  *cycles = 3;
481	  /* Indexed.  */
482	  dst = msp430dis_opcode (addr + 2, info);
483	  cmd_len += 2;
484	  sprintf (op1, "%d(r%d)", dst, regs);
485	}
486    }
487
488  /* Destination. Special care needed on addr + XXXX.  */
489
490  if (ad == 0)
491    {
492      /* Register.  */
493      if (regd == 0)
494	{
495	  *cycles += 1;
496	  sprintf (op2, "r0");
497	}
498      else if (regd == 1)
499	sprintf (op2, "r1");
500
501      else if (regd == 2)
502	sprintf (op2, "r2");
503
504      else
505	sprintf (op2, "r%d", regd);
506    }
507  else	/* ad == 1.  */
508    {
509      * cycles += 3;
510
511      if (regd == 0)
512	{
513	  /* PC relative.  */
514	  *cycles += 1;
515	  dst = msp430dis_opcode (addr + cmd_len, info);
516	  sprintf (op2, "0x%04x", PS (dst));
517	  sprintf (comm2, "PC rel. 0x%04x",
518		   PS ((short) addr + cmd_len + dst));
519	  cmd_len += 2;
520	}
521      else if (regd == 2)
522	{
523	  /* Absolute.  */
524	  dst = msp430dis_opcode (addr + cmd_len, info);
525	  cmd_len += 2;
526	  sprintf (op2, "&0x%04x", PS (dst));
527	}
528      else
529	{
530	  dst = msp430dis_opcode (addr + cmd_len, info);
531	  cmd_len += 2;
532	  sprintf (op2, "%d(r%d)", dst, regd);
533	}
534    }
535
536  return cmd_len;
537}
538
539static int
540msp430_branchinstr (disassemble_info *info,
541		    struct msp430_opcode_s *opcode ATTRIBUTE_UNUSED,
542		    bfd_vma addr ATTRIBUTE_UNUSED,
543		    unsigned short insn,
544		    char *op1,
545		    char *comm1,
546		    int *cycles)
547{
548  int regs = 0, regd = 0;
549  int ad = 0, as = 0;
550  int cmd_len = 2;
551  short dst = 0;
552
553  regd = insn & 0x0f;
554  regs = (insn & 0x0f00) >> 8;
555  as = (insn & 0x0030) >> 4;
556  ad = (insn & 0x0080) >> 7;
557
558  if (regd != 0)	/* Destination register is not a PC.  */
559    return 0;
560
561  /* dst is a source register.  */
562  if (as == 0)
563    {
564      /* Constants.  */
565      if (regs == 3)
566	{
567	  *cycles = 1;
568	  sprintf (op1, "#0");
569	  sprintf (comm1, "r3 As==00");
570	}
571      else
572	{
573	  /* Register.  */
574	  *cycles = 1;
575	  sprintf (op1, "r%d", regs);
576	}
577    }
578  else if (as == 2)
579    {
580      if (regs == 2)
581	{
582	  *cycles = 2;
583	  sprintf (op1, "#4");
584	  sprintf (comm1, "r2 As==10");
585	}
586      else if (regs == 3)
587	{
588	  *cycles = 1;
589	  sprintf (op1, "#2");
590	  sprintf (comm1, "r3 As==10");
591	}
592      else
593	{
594	  /* Indexed register mode @Rn.  */
595	  *cycles = 2;
596	  sprintf (op1, "@r%d", regs);
597	}
598    }
599  else if (as == 3)
600    {
601      if (regs == 2)
602	{
603	  *cycles = 1;
604	  sprintf (op1, "#8");
605	  sprintf (comm1, "r2 As==11");
606	}
607      else if (regs == 3)
608	{
609	  *cycles = 1;
610	  sprintf (op1, "#-1");
611	  sprintf (comm1, "r3 As==11");
612	}
613      else if (regs == 0)
614	{
615	  /* Absolute. @pc+  */
616	  *cycles = 3;
617	  dst = msp430dis_opcode (addr + 2, info);
618	  cmd_len += 2;
619	  sprintf (op1, "#0x%04x", PS (dst));
620	}
621      else
622	{
623	  *cycles = 2;
624	  sprintf (op1, "@r%d+", regs);
625	}
626    }
627  else if (as == 1)
628    {
629      * cycles = 3;
630
631      if (regs == 0)
632	{
633	  /* PC relative.  */
634	  dst = msp430dis_opcode (addr + 2, info);
635	  cmd_len += 2;
636	  (*cycles)++;
637	  sprintf (op1, "0x%04x", PS (dst));
638	  sprintf (comm1, "PC rel. 0x%04x",
639		   PS ((short) addr + 2 + dst));
640	}
641      else if (regs == 2)
642	{
643	  /* Absolute.  */
644	  dst = msp430dis_opcode (addr + 2, info);
645	  cmd_len += 2;
646	  sprintf (op1, "&0x%04x", PS (dst));
647	}
648      else if (regs == 3)
649	{
650	  (*cycles)--;
651	  sprintf (op1, "#1");
652	  sprintf (comm1, "r3 As==01");
653	}
654      else
655	{
656	  /* Indexd.  */
657	  dst = msp430dis_opcode (addr + 2, info);
658	  cmd_len += 2;
659	  sprintf (op1, "%d(r%d)", dst, regs);
660	}
661    }
662
663  return cmd_len;
664}
665
666int
667print_insn_msp430 (bfd_vma addr, disassemble_info *info)
668{
669  void *stream = info->stream;
670  fprintf_ftype prin = info->fprintf_func;
671  struct msp430_opcode_s *opcode;
672  char op1[32], op2[32], comm1[64], comm2[64];
673  int cmd_len = 0;
674  unsigned short insn;
675  int cycles = 0;
676  char *bc = "";
677  char dinfo[32];		/* Debug purposes.  */
678
679  insn = msp430dis_opcode (addr, info);
680  sprintf (dinfo, "0x%04x", insn);
681
682  if (((int) addr & 0xffff) > 0xffdf)
683    {
684      (*prin) (stream, "interrupt service routine at 0x%04x", 0xffff & insn);
685      return 2;
686    }
687
688  *comm1 = 0;
689  *comm2 = 0;
690
691  for (opcode = msp430_opcodes; opcode->name; opcode++)
692    {
693      if ((insn & opcode->bin_mask) == opcode->bin_opcode
694	  && opcode->bin_opcode != 0x9300)
695	{
696	  *op1 = 0;
697	  *op2 = 0;
698	  *comm1 = 0;
699	  *comm2 = 0;
700
701	  /* r0 as destination. Ad should be zero.  */
702	  if (opcode->insn_opnumb == 3 && (insn & 0x000f) == 0
703	      && (0x0080 & insn) == 0)
704	    {
705	      cmd_len =
706		msp430_branchinstr (info, opcode, addr, insn, op1, comm1,
707				    &cycles);
708	      if (cmd_len)
709		break;
710	    }
711
712	  switch (opcode->insn_opnumb)
713	    {
714	    case 0:
715	      cmd_len = msp430_nooperands (opcode, addr, insn, comm1, &cycles);
716	      break;
717	    case 2:
718	      cmd_len =
719		msp430_doubleoperand (info, opcode, addr, insn, op1, op2,
720				      comm1, comm2, &cycles);
721	      if (insn & BYTE_OPERATION)
722		bc = ".b";
723	      break;
724	    case 1:
725	      cmd_len =
726		msp430_singleoperand (info, opcode, addr, insn, op1, comm1,
727				      &cycles);
728	      if (insn & BYTE_OPERATION && opcode->fmt != 3)
729		bc = ".b";
730	      break;
731	    default:
732	      break;
733	    }
734	}
735
736      if (cmd_len)
737	break;
738    }
739
740  dinfo[5] = 0;
741
742  if (cmd_len < 1)
743    {
744      /* Unknown opcode, or invalid combination of operands.  */
745      (*prin) (stream, ".word	0x%04x;	????", PS (insn));
746      return 2;
747    }
748
749  (*prin) (stream, "%s%s", opcode->name, bc);
750
751  if (*op1)
752    (*prin) (stream, "\t%s", op1);
753  if (*op2)
754    (*prin) (stream, ",");
755
756  if (strlen (op1) < 7)
757    (*prin) (stream, "\t");
758  if (!strlen (op1))
759    (*prin) (stream, "\t");
760
761  if (*op2)
762    (*prin) (stream, "%s", op2);
763  if (strlen (op2) < 8)
764    (*prin) (stream, "\t");
765
766  if (*comm1 || *comm2)
767    (*prin) (stream, ";");
768  else if (cycles)
769    {
770      if (*op2)
771	(*prin) (stream, ";");
772      else
773	{
774	  if (strlen (op1) < 7)
775	    (*prin) (stream, ";");
776	  else
777	    (*prin) (stream, "\t;");
778	}
779    }
780  if (*comm1)
781    (*prin) (stream, "%s", comm1);
782  if (*comm1 && *comm2)
783    (*prin) (stream, ",");
784  if (*comm2)
785    (*prin) (stream, " %s", comm2);
786  return cmd_len;
787}
788