//! Insert breakpoint
//!
//! @param type     one of memoryBreak, hardBreak, writeWatch, readWatch, accessWatch
//! @param address  address for breakpoint/watchpoint
//! @param size     range to watch (watchpoint only)
//!
//! @return true  => OK\n
//!         false => error
//!
//! @note writeWatch, readWatch, accessWatch not currently working
//!
int GdbBreakpoints::insertBreakpoint(BreakType type, uint32_t address, unsigned size) {
   LOGGING_Q;
   log.print("(%s, a=%08X, s=%d)\n", getBreakpointName(type), address, size);

   switch (type) {
   case memoryBreak: {
      if (findMemoryBreakpoint(address)) {
         log.print("- already set - ignored\n");
         return true; // Already set - ignore
      }
      MemoryBreakInfo *bpPtr = findFreeMemoryBreakpoint();
      if (bpPtr == NULL) {
         return false; // Too many breakpoints
      }
      bpPtr->address = address;
      bpPtr->inUse   = true;
      return true; // Done
      }
      break;
   case hardBreak: {
      if (findHardwareBreakpoint(address)) {
         log.print("- already set - ignored\n");
         return true; // Already set - ignore
      }
      HardwareBreakInfo *bpPtr = findFreeHardwareBreakpoint();
      if (bpPtr == NULL) {
         return false; // Too many breakpoints
      }
      bpPtr->address = address;
      bpPtr->inUse   = true;
      return true; // Done
      }
      break;
   case writeWatch:
   case readWatch:
   case accessWatch: {
      DataBreakInfo *bpPtr = findDataWatchPoint(address);
      if (bpPtr != NULL) {
         if (size > bpPtr->size)
            bpPtr->size = size;
         if (type != bpPtr->type)
            bpPtr->type = accessWatch;
         return true; // Already set - update
      }
      bpPtr = findFreeDataWatchPoint();
      if (bpPtr == NULL) {
         return false; // Too many breakpoints
      }
      bpPtr->address = address;
      bpPtr->size    = size;
      bpPtr->type    = (BreakType)type;
      bpPtr->inUse   = true;
      return true; // Done
      }
      break;
   default:
      return false; // Unknown type
   }
}
//! Remove breakpoint
//!
//! @param type     one of memoryBreak, hardBreak, writeWatch, readWatch, accessWatch
//! @param address  address for breakpoint/watchpoint
//! @param size     not used
//!
int GdbBreakpoints::removeBreakpoint(BreakType type, uint32_t address, unsigned size) {
   LOGGING_Q;
   log.print("(%s, a=%08X, s=%d)\n", getBreakpointName(type), address, size);
   switch (type) {
   case memoryBreak: {
      MemoryBreakInfo *bpPtr = findMemoryBreakpoint(address);
      if (bpPtr == NULL) {
         return false; // Non-existent breakpoint
      }
      bpPtr->inUse     = false;
      bpPtr->address   = 0;
      bpPtr->opcode[0] = 0;
      bpPtr->opcode[1] = 0;
      log.print("(t=MEM, a=%08X, s=%d) - done\n", address, size);
      return true; // Done
   }
   break;
   case hardBreak: {
      HardwareBreakInfo *bpPtr = findHardwareBreakpoint(address);
      if (bpPtr == NULL) {
         return false; // Non-existent breakpoint
      }
      bpPtr->inUse     = false;
      log.print("(t=HW, a=%08X, s=%d) - done\n", address, size);
      return true; // Done
      }
      break;
   case writeWatch:
   case readWatch:
   case accessWatch: {
      DataBreakInfo *bpPtr = findDataWatchPoint(address);
      if (bpPtr == NULL) {
         return false; // Non-existent breakpoint
      }
      bpPtr->inUse     = false;
      return true; // Done
      }
      break;
   default:
      return false; // Unknown type
   }
}
//! Activate breakpoints. \n
//! This may involve changing target code for RAM breakpoints or
//! modifying target breakpoint hardware
//!
void activateBreakpoints(void) {
   print("activateBreakpoints()\n");
   memoryBreakInfo *bpPtr;
   if (breakpointsActive)
      return;
   // Memory breakpoints
   for (bpPtr = memoryBreakpoints;
        bpPtr < memoryBreakpoints+MAX_MEMORY_BREAKPOINTS;
        bpPtr++) {
      if (bpPtr->inUse) {
         print("activateBreakpoints(%s@%08X)\n", getBreakpointName(memoryBreak), bpPtr->address);
         USBDM_ReadMemory(sizeof(haltOpcode),sizeof(haltOpcode),bpPtr->address,bpPtr->opcode);
         USBDM_WriteMemory(sizeof(haltOpcode),sizeof(haltOpcode),bpPtr->address,haltOpcode);
         breakpointsActive = true;
      }
   }
   // Hardware breakpoints
   uint32_t fp_ctrl = FP_CTRL_DISABLE;
   for (int breakPtNum=0; breakPtNum<MAX_HARDWARE_BREAKPOINTS; breakPtNum++) {
      if (hardwareBreakpoints[breakPtNum].inUse) {
         print("activateBreakpoints(%s@%08X)\n", getBreakpointName(hardBreak),
                                                 hardwareBreakpoints[breakPtNum].address&~0x1);
         fp_ctrl = FP_CTRL_ENABLE;
         ARM_WriteMemory(4, 4, FP_COMP0+4*breakPtNum, getFpCompAddress(hardwareBreakpoints[breakPtNum].address));
         breakpointsActive = true;
      }
      else {
         ARM_WriteMemory(4, 4, FP_COMP0+4*breakPtNum, getData4x8Le(FP_COMP_DISABLE));
      }
   }
   ARM_WriteMemory(4, 4, FP_CTRL, getData4x8Le(fp_ctrl));
   // Hardware watches
   for (int watchPtNum=0; watchPtNum<MAX_DATA_WATCHES; watchPtNum++) {
      if (dataWatchPoints[watchPtNum].inUse) {
         unsigned size       = dataWatchPoints[watchPtNum].size;
         unsigned bpSize     = 1;
         unsigned sizeValue  = 0;
         while ((bpSize<size) && (sizeValue<15)) {
            sizeValue++;
            bpSize <<= 1;
         }
         int mode = DWT_FUNCTION_READ_WATCH;
         switch (dataWatchPoints[watchPtNum].type) {
         case readWatch:   mode = DWT_FUNCTION_READ_WATCH;  break;
         case writeWatch:  mode = DWT_FUNCTION_WRITE_WATCH; break;
         case accessWatch: mode = DWT_FUNCTION_RW_WATCH;    break;
         default : break;
         }
         print("activateBreakpoints(%s@%08X)\n", getBreakpointName(dataWatchPoints[watchPtNum].type),
                                                 dataWatchPoints[watchPtNum].address);
         print("activateBreakpoints(%s@%08X, bpSize=%d, sizeValue=%d)\n",
               getBreakpointName(dataWatchPoints[watchPtNum].type),
               dataWatchPoints[watchPtNum].address&(~(bpSize-1)),
               bpSize, sizeValue );
         ARM_WriteMemory(4, 4, DWT_COMP0+16*watchPtNum,     getData4x8Le(dataWatchPoints[watchPtNum].address));
         ARM_WriteMemory(4, 4, DWT_MASK0+16*watchPtNum,     getData4x8Le(sizeValue));
         ARM_WriteMemory(4, 4, DWT_FUNCTION0+16*watchPtNum, getData4x8Le(mode));
         breakpointsActive = true;
      }
      else {
         ARM_WriteMemory(4, 4, DWT_FUNCTION0+16*watchPtNum, getData4x8Le(DWT_FUNCTION_NONE));
      }
   }
}
//! Activate breakpoints. \n
//! This may involve changing target code for RAM breakpoints or
//! modifying target breakpoint hardware
//!
void activateBreakpoints(void) {
   print("activateBreakpoints()\n");
   static const uint8_t haltOpcode[] = {0x4a, 0xc8};
   memoryBreakInfo *bpPtr;
   if (breakpointsActive)
      return;
   for (bpPtr = memoryBreakpoints;
        bpPtr < memoryBreakpoints+MAX_MEMORY_BREAKPOINTS;
        bpPtr++) {
      if (bpPtr->inUse) {
         print("activateBreakpoints(%s@%08X)\n", getBreakpointName(memoryBreak), bpPtr->address);
         USBDM_ReadMemory(2,2,bpPtr->address,bpPtr->opcode);
         USBDM_WriteMemory(2,2,bpPtr->address,haltOpcode);
         breakpointsActive = true;
      }
   }
   uint32_t tdrValue = TDR_DISABLE;
   if (hardwareBreakpoints[0].inUse) {
      tdrValue |= TDR_TRC_HALT|TDR_L1T|TDR_L1EBL|TDR_L1EPC;
      USBDM_WriteDReg(CFVx_DRegPBR0, hardwareBreakpoints[0].address&~0x1);
      USBDM_WriteDReg(CFVx_DRegPBMR, 0x00000000);
      breakpointsActive = true;
      print("activateBreakpoints(%s@%08X)\n", getBreakpointName(hardBreak),
                                              hardwareBreakpoints[0].address&~0x1);
   }
   if (hardwareBreakpoints[1].inUse) {
      tdrValue |= TDR_TRC_HALT|TDR_L1T|TDR_L1EBL|TDR_L1EPC;
      USBDM_WriteDReg(CFVx_DRegPBR1, hardwareBreakpoints[1].address|0x1);
      breakpointsActive = true;
      print("activateBreakpoints(%s@%08X)\n", getBreakpointName(hardBreak),
                                              hardwareBreakpoints[1].address&~0x1);
   }
   else {
      USBDM_WriteDReg(CFVx_DRegPBR1,0);
   }
   if (hardwareBreakpoints[2].inUse) {
      tdrValue |= TDR_TRC_HALT|TDR_L1T|TDR_L1EBL|TDR_L1EPC;
      USBDM_WriteDReg(CFVx_DRegPBR2, hardwareBreakpoints[2].address|0x1);
      breakpointsActive = true;
      print("activateBreakpoints(%s@%08X)\n", getBreakpointName(hardBreak),
                                              hardwareBreakpoints[2].address&~0x1);
   }
   else {
      USBDM_WriteDReg(CFVx_DRegPBR2,0);
   }
   if (hardwareBreakpoints[3].inUse) {
      tdrValue |= TDR_TRC_HALT|TDR_L1T|TDR_L1EBL|TDR_L1EPC;
      USBDM_WriteDReg(CFVx_DRegPBR3, hardwareBreakpoints[3].address|0x1);
      breakpointsActive = true;
      print("activateBreakpoints(%s@%08X)\n", getBreakpointName(hardBreak),
                                              hardwareBreakpoints[3].address&~0x1);
   }
   else {
      USBDM_WriteDReg(CFV1_DRegPBR3,0);
   }
   if (dataWatchPoints[0].inUse) {
      tdrValue |= TDR_TRC_HALT|TDR_L1T|TDR_L1EBL|TDR_L1EA_INC;
      USBDM_WriteDReg(CFVx_DRegABLR, dataWatchPoints[0].address);
      USBDM_WriteDReg(CFVx_DRegABHR, dataWatchPoints[0].address+dataWatchPoints[0].size-1);
      breakpointsActive = true;
   }
   USBDM_WriteDReg(CFVx_DRegTDR, tdrValue);
}