Repository: square/pylink

Issue:
Programming without erasing chip
We have a system where the bootloader and some additional data need to be retained on the flash while the application can be erased. Using chip erase will nuke all of it.

I've been digging through the SDK documentation and it looks like it's possible to do using Begin- and EndDownload. It would be something like this:

```Python
data = open(path, mode="rb").read()
self._dll.JLINKARM_BeginDownload(0)
self._dll.JLINKARM_WriteMem(addr, len(data), data)
self._dll.JLINKARM.EndDownload()
```

I could give this a shot, but how would you like it implemented? Integrated into the current flash operations (`flash` and `flash_file`) or as a new function? I don't think it's possible to get a progress bar in that case, so a new function might be in order.

Discussion:
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/square/pylink?pullRequest=155) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/square/pylink?pullRequest=155) before we can accept your contribution.<br/><hr/>**Marco Nilsson** seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/square/pylink?pullRequest=155) it.</sub>
hkpeprah: Trying to figure out why the tests are failing with `ubuntu-latest, 2.7`.

Patch:
diff --git a/pylink/jlink.py b/pylink/jlink.py
index b5343b3..d8c9e86 100644
--- a/pylink/jlink.py
+++ b/pylink/jlink.py
@@ -2157,7 +2157,7 @@ class JLink(object):
 
         Args:
           self (JLink): the ``JLink`` instance
-          data (list): list of bytes to write to flash
+          data (list|bytes): list or byte object of bytes to write to flash
           addr (int): start address on flash which to write the data
           on_progress (function): callback to be triggered on flash progress
           power_on (boolean): whether to power the target before flashing
@@ -2192,9 +2192,21 @@ class JLink(object):
         except errors.JLinkException:
             pass
 
-        res = self.flash_write(addr, data, flags=flags)
+        # Perform read-modify-write operation.
+        res = self._dll.JLINKARM_BeginDownload(flags=flags)
+        if res < 0:
+            raise errors.JLinkEraseException(res)
 
-        return res
+        if isinstance(data, list):
+            data = bytes(data)
+
+        bytes_flashed = self._dll.JLINKARM_WriteMem(addr, len(data), data)
+
+        res = self._dll.JLINKARM_EndDownload()
+        if res < 0:
+            raise errors.JLinkEraseException(res)
+
+        return bytes_flashed
 
     @connection_required
     def flash_file(self, path, addr, on_progress=None, power_on=False):
diff --git a/pylink/library.py b/pylink/library.py
index dd2d2d3..67e1ed6 100644
--- a/pylink/library.py
+++ b/pylink/library.py
@@ -80,7 +80,10 @@ class Library(object):
         'JLINK_SWD_StoreRaw',
         'JLINK_SWD_SyncBits',
         'JLINK_SWD_SyncBytes',
-        'JLINK_SetFlashProgProgressCallback'
+        'JLINK_SetFlashProgProgressCallback',
+        'JLINKARM_BeginDownload',
+        'JLINKARM_EndDownload',
+        'JLINKARM_WriteMem'
     ]
 
     # On Linux and macOS, represents the library file name prefix


Test patch:
diff --git a/tests/unit/test_jlink.py b/tests/unit/test_jlink.py
index fa7c32f..e6a7e51 100644
--- a/tests/unit/test_jlink.py
+++ b/tests/unit/test_jlink.py
@@ -2715,7 +2715,7 @@ class TestJLink(unittest.TestCase):
         Returns:
           ``None``
         """
-        self.dll.JLINKARM_EndDownload.return_value = -1
+        self.dll.JLINKARM_WriteMem.return_value = 0
 
         self.jlink.power_on = mock.Mock()
         self.jlink.erase = mock.Mock()
@@ -2724,6 +2724,15 @@ class TestJLink(unittest.TestCase):
         self.jlink.halted = mock.Mock()
         self.jlink.halted.return_value = True
 
+        # BeginDownload failing
+        self.dll.JLINKARM_BeginDownload.return_value = -1
+        self.dll.JLINKARM_EndDownload.return_value = 0
+        with self.assertRaises(JLinkException):
+            self.jlink.flash([0], 0)
+
+        # EndDownload failing
+        self.dll.JLINKARM_BeginDownload.return_value = 0
+        self.dll.JLINKARM_EndDownload.return_value = -1
         with self.assertRaises(JLinkException):
             self.jlink.flash([0], 0)
 
@@ -2736,6 +2745,8 @@ class TestJLink(unittest.TestCase):
         Returns:
           ``None``
         """
+        self.dll.JLINKARM_BeginDownload.return_value = 0
+        self.dll.JLINKARM_WriteMem.return_value = 0
         self.dll.JLINKARM_EndDownload.return_value = 0
 
         self.jlink.power_on = mock.Mock()